diff --git a/CMakeLists.txt b/CMakeLists.txt index 85d7261e9b..1457bcd559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,8 +274,8 @@ option(USE_CPLEX "Use the CPLEX solver" OFF) message(STATUS "CPLEX support: ${USE_CPLEX}") ## XPRESS -option(USE_XPRESS "Use the XPRESS solver" OFF) -message(STATUS "XPRESS support: ${USE_XPRESS}") +# Since it is dynamicaly loaded upon use, OFF is currently not supported. +CMAKE_DEPENDENT_OPTION(USE_XPRESS "Use the Xpress solver" ON "BUILD_CXX" OFF) # Language specific options if(BUILD_CXX) diff --git a/cmake/FindXPRESS.cmake b/cmake/FindXPRESS.cmake deleted file mode 100644 index a7678366af..0000000000 --- a/cmake/FindXPRESS.cmake +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2010-2022 Google LLC -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -#[=======================================================================[.rst: -FindXPRESS --------- - -This module determines the XPRESS library of the system. - -IMPORTED Targets -^^^^^^^^^^^^^^^^ - -This module defines :prop_tgt:`IMPORTED` target ``XPRESS::XPRESS``, if -XPRESS has been found. - -Result Variables -^^^^^^^^^^^^^^^^ - -This module defines the following variables: - -:: - -XPRESS_FOUND - True if XPRESS found. - -Hints -^^^^^ - -A user may set ``XPRESS_ROOT`` to a XPRESS installation root to tell this -module where to look. -#]=======================================================================] -set(XPRESS_FOUND FALSE) - -if(CMAKE_C_COMPILER_LOADED) - include (CheckIncludeFile) - include (CheckCSourceCompiles) -elseif(CMAKE_CXX_COMPILER_LOADED) - include (CheckIncludeFileCXX) - include (CheckCXXSourceCompiles) -else() - message(FATAL_ERROR "FindXPRESS only works if either C or CXX language is enabled") -endif() - -if(NOT XPRESS_ROOT) - set(XPRESS_ROOT $ENV{XPRESS_ROOT}) -endif() -message(STATUS "XPRESS_ROOT: ${XPRESS_ROOT}") -if(NOT XPRESS_ROOT) - message(FATAL_ERROR "XPRESS_ROOT: not found") -else() - set(XPRESS_FOUND TRUE) -endif() - -if(XPRESS_FOUND AND NOT TARGET XPRESS::XPRESS) - add_library(XPRESS::XPRESS UNKNOWN IMPORTED) - - if(UNIX) - target_include_directories(XPRESS::XPRESS SYSTEM INTERFACE "${XPRESS_ROOT}/include") - endif() - - if(APPLE) # be aware that `UNIX` is `TRUE` on OS X, so this check must be first - set_target_properties(XPRESS::XPRESS PROPERTIES - #INSTALL_RPATH_USE_LINK_PATH TRUE - #BUILD_WITH_INSTALL_RPATH TRUE - #INTERFACE_LINK_DIRECTORIES "${XPRESS_ROOT}/lib" - #INSTALL_RPATH "${XPRESS_ROOT}/lib;${INSTALL_RPATH}" - IMPORTED_LOCATION "${XPRESS_ROOT}/lib/libxprs.dylib") - elseif(UNIX) - set_target_properties(XPRESS::XPRESS PROPERTIES - INTERFACE_LINK_DIRECTORIES "${XPRESS_ROOT}/lib" - IMPORTED_LOCATION ${XPRESS_ROOT}/lib/libxprs.so) - elseif(MSVC) - set_target_properties(XPRESS::XPRESS PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${XPRESS_ROOT}\\include" - IMPORTED_LOCATION "${XPRESS_ROOT}\\lib\\xprs.lib") - else() - message(FATAL_ERROR "XPRESS not supported for ${CMAKE_SYSTEM}") - endif() -endif() diff --git a/cmake/README.md b/cmake/README.md index 76c7dcca4c..4d09125fcc 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -91,7 +91,7 @@ Here the list of supported solvers: * HiGHS\* * PDLP * SCIP -* XPRESS\* +* XPRESS \*: these solvers are disabled by default. @@ -127,7 +127,6 @@ support for the following third-party solvers: note: You must enable the support of GLPK solver by using `-DUSE_GLPK=ON` (`OFF` by default). * CPLEX (`USE_CPLEX`), -* XPRESS (`USE_XPRESS`) **warning: Since these solvers are either proprietary (and require a specific license) or available under the GPL, we can't test them on public CI and their @@ -145,14 +144,6 @@ For ease of migration from legacy `make third_party` builds, CMake will also read the CPLEX installation path from the `UNIX_CPLEX_DIR` environment variable, if defined. -### Enabling XPRESS Support - -To enable XPRESS support, configure with `-DUSE_XPRESS=ON` and -`-DXPRESS_ROOT=/absolute/path/to/XPRESS/root/dir`, replacing -`/absolute/path/to/XPRESS/root/dir` with the path to your XPRESS installation. -`XPRESS_ROOT` can also be defined as an environment variable rather than an -option at configure time. - ## CMake Options There are several options that can be passed to CMake to modify how the code @@ -217,8 +208,6 @@ cmake -S. -Bbuild -LH | | | | | `USE_CPLEX` | OFF | Enable CPLEX support | | | | | -| `USE_XPRESS` | OFF | Enable XPRESS support | -| | | | | `BUILD_DOC` | OFF\* | Build all documentations | | `BUILD_CXX_DOC` | OFF\* | Build C++ documentation
**Forced** to ON if `BUILD_DOC=ON` | | `BUILD_DOTNET_DOC` | OFF\* | Build .Net documentation
**Forced** to ON if `BUILD_DOC=ON` | diff --git a/cmake/cpp.cmake b/cmake/cpp.cmake index 8ea55a8854..78b19b9a48 100644 --- a/cmake/cpp.cmake +++ b/cmake/cpp.cmake @@ -66,14 +66,6 @@ endif() if(USE_CPLEX) list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_CPLEX") endif() -if(USE_XPRESS) - list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "USE_XPRESS") - if(MSVC) - list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "XPRESS_PATH=\"${XPRESS_ROOT}\"") - else() - list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "XPRESS_PATH=${XPRESS_ROOT}") - endif() -endif() if(WIN32) list(APPEND OR_TOOLS_COMPILE_DEFINITIONS "__WIN32__") @@ -327,7 +319,8 @@ foreach(SUBPROJECT IN ITEMS port sat scheduling - util) + util + xpress) add_subdirectory(ortools/${SUBPROJECT}) #target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_${SUBPROJECT}) target_sources(${PROJECT_NAME} PRIVATE $) @@ -365,7 +358,6 @@ target_link_libraries(${PROJECT_NAME} PUBLIC $<$:HIGHS::HIGHS> ${PDLP_DEPS} $<$:libscip> - $<$:XPRESS::XPRESS> Threads::Threads) if(WIN32) target_link_libraries(${PROJECT_NAME} PUBLIC psapi.lib ws2_32.lib) diff --git a/cmake/deps.cmake b/cmake/deps.cmake index 918649d56f..4b4579abce 100644 --- a/cmake/deps.cmake +++ b/cmake/deps.cmake @@ -145,10 +145,6 @@ if(USE_CPLEX) find_package(CPLEX REQUIRED) endif() -if(USE_XPRESS) - find_package(XPRESS REQUIRED) -endif() - # Check language Dependencies if(BUILD_PYTHON) if(NOT BUILD_pybind11) diff --git a/examples/cpp/integer_programming.cc b/examples/cpp/integer_programming.cc index 86400484f4..58b22fb332 100644 --- a/examples/cpp/integer_programming.cc +++ b/examples/cpp/integer_programming.cc @@ -87,6 +87,7 @@ void RunAllExamples() { RunIntegerProgrammingExample("GUROBI"); RunIntegerProgrammingExample("GLPK"); RunIntegerProgrammingExample("CPLEX"); + RunIntegerProgrammingExample("XPRESS"); } } // namespace operations_research diff --git a/examples/cpp/xpress_use.cc b/examples/cpp/xpress_use.cc new file mode 100644 index 0000000000..600918e762 --- /dev/null +++ b/examples/cpp/xpress_use.cc @@ -0,0 +1,104 @@ +// Copyright Artelys for RTE. +// 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. + +// This example that shows how to use Xpress Solver. + +#include + +#include "ortools/base/commandlineflags.h" +#include "ortools/base/init_google.h" +#include "ortools/base/logging.h" +#include "ortools/linear_solver/linear_solver.h" + +using namespace operations_research; + +/** + * This method shows two ways to initialize a Xpress solver instance. + * Two environment variables are used to specify the Xpress installation paths: + * * XPRESSDIR : Path to the Xpress root directory (containing bin and lib folders) + * * XPRESS : Path to the directory containing Xpress license + */ +void useXpressSolver(bool solveAsMip, bool useFactory) { + std::unique_ptr solver = nullptr; + if (useFactory) { + /* This is the preferred way as the program won't stop if anything went + wrong. In such a case, `solver` will take value `nullptr` */ + std::string xpressName = (solveAsMip ? "XPRESS" : "XPRESS_LP"); + solver.reset(MPSolver::CreateSolver(xpressName)); + } else { + MPSolver::OptimizationProblemType problemType = (solveAsMip ? + MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING + : MPSolver::XPRESS_LINEAR_PROGRAMMING); + /* MPSolver::SupportsProblemType(problem_type) will test if Xpress is + correctly loaded and has a valid license. This check is important to keep + the program running if Xpress is not correctly installed. With the + constructor usage, if Xpress is badly loaded or if there is a problem + with the license, the program will abort (SIGABRT) + */ + if (MPSolver::SupportsProblemType(problemType)) { + solver.reset(new MPSolver("IntegerProgrammingExample", problemType)); + } + } + if (solver == nullptr) { + LOG(WARNING) << "Xpress solver is not available"; + return; + } + // Use the solver + /* + max -100 x1 + 10 x2 + s.t. x2 <= 20 x1; + 30 x1 + 3.5 x2 <= 350 + 0 <= x1 <= 5 + 0 <= x2 + */ + const double infinity = MPSolver::infinity(); + const MPVariable* x1 = solver->MakeIntVar(0, 5, "x1"); + const MPVariable* x2 = solver->MakeNumVar(0.0, infinity, "x2"); + + MPObjective* const objective = solver->MutableObjective(); + objective->SetCoefficient(x1, -100); + objective->SetCoefficient(x2, 10); + objective->SetMaximization(); + + MPConstraint* const c0 = solver->MakeRowConstraint(-infinity, 0.0); + c0->SetCoefficient(x1, -20.0); + c0->SetCoefficient(x2, 1); + + MPConstraint* const c1 = solver->MakeRowConstraint(-infinity, 350.0); + c1->SetCoefficient(x1, 30.0); + c1->SetCoefficient(x2, 3.5); + + + const MPSolver::ResultStatus result_status = solver->Solve(); + + // Check that the problem has an optimal solution. + if (result_status != MPSolver::OPTIMAL) { + LOG(FATAL) << "Solver returned with non-optimal status."; + } else { + LOG(WARNING) << "Optimal solution found: obj=" << objective->Value(); + } +} +#define ABSL_MIN_LOG_LEVEL INFO; +int main(int argc, char** argv) { + absl::SetFlag(&FLAGS_stderrthreshold, 0); + absl::SetFlag(&FLAGS_logtostderr, true); + InitGoogle(argv[0], &argc, &argv, true); + std::cout << "start\n"; + LOG(WARNING) << "start"; + for (bool solveAsMip: {true, false}) { + for (bool useFactory: {true, false}) { + useXpressSolver(solveAsMip, useFactory); + } + } + return EXIT_SUCCESS; +} diff --git a/examples/dotnet/csintegerprogramming.cs b/examples/dotnet/csintegerprogramming.cs index d3e1041e99..ee61c75c12 100644 --- a/examples/dotnet/csintegerprogramming.cs +++ b/examples/dotnet/csintegerprogramming.cs @@ -117,6 +117,8 @@ public class CsIntegerProgramming RunIntegerProgrammingExample("SAT"); Console.WriteLine("---- Linear programming example with GUROBI ----"); RunIntegerProgrammingExample("GUROBI"); + Console.WriteLine("---- Linear programming example with XPRESS ----"); + RunIntegerProgrammingExample("XPRESS"); Console.WriteLine("---- Integer programming example (Natural API) with GLPK ----"); RunIntegerProgrammingExampleNaturalApi("GLPK"); Console.WriteLine("---- Linear programming example (Natural API) with CBC ----"); @@ -127,5 +129,7 @@ public class CsIntegerProgramming RunIntegerProgrammingExampleNaturalApi("SAT"); Console.WriteLine("---- Linear programming example (Natural API) with GUROBI ----"); RunIntegerProgrammingExampleNaturalApi("GUROBI"); + Console.WriteLine("---- Linear programming example (Natural API) with XPRESS ----"); + RunIntegerProgrammingExampleNaturalApi("XPRESS"); } } diff --git a/examples/dotnet/cslinearprogramming.cs b/examples/dotnet/cslinearprogramming.cs index c3f2188db0..3872550cc8 100644 --- a/examples/dotnet/cslinearprogramming.cs +++ b/examples/dotnet/cslinearprogramming.cs @@ -160,6 +160,7 @@ public class CsLinearProgramming RunLinearProgrammingExample("GLOP"); RunLinearProgrammingExample("GLPK_LP"); RunLinearProgrammingExample("CLP"); + RunLinearProgrammingExample("XPRESS_LP"); RunLinearProgrammingExampleNaturalApi("GLOP", true); RunLinearProgrammingExampleNaturalApi("GLPK_LP", false); diff --git a/examples/java/IntegerProgramming.java b/examples/java/IntegerProgramming.java index 6e5ede6ee8..7e730f48f8 100644 --- a/examples/java/IntegerProgramming.java +++ b/examples/java/IntegerProgramming.java @@ -84,5 +84,7 @@ public class IntegerProgramming { runIntegerProgrammingExample("GLPK"); System.out.println("---- Integer programming example with CP-SAT ----"); runIntegerProgrammingExample("SAT"); + System.out.println("---- Integer programming example with XPRESS ----"); + runIntegerProgrammingExample("XPRESS"); } } diff --git a/examples/java/LinearProgramming.java b/examples/java/LinearProgramming.java index 8457d77435..34749e4046 100644 --- a/examples/java/LinearProgramming.java +++ b/examples/java/LinearProgramming.java @@ -116,5 +116,7 @@ public class LinearProgramming { runLinearProgrammingExample("GLOP", true); System.out.println("---- Linear programming example with CLP ----"); runLinearProgrammingExample("CLP", false); + System.out.println("---- Linear programming example with XPRESS ----"); + runLinearProgrammingExample("XPRESS_LP", false); } } diff --git a/examples/python/integer_programming.py b/examples/python/integer_programming.py index 21d62a1985..c61f90258d 100755 --- a/examples/python/integer_programming.py +++ b/examples/python/integer_programming.py @@ -102,6 +102,7 @@ def RunAllIntegerExampleNaturalLanguageAPI(): # RunIntegerExampleNaturalLanguageAPI('CBC') RunIntegerExampleNaturalLanguageAPI("SCIP") RunIntegerExampleNaturalLanguageAPI("SAT") + RunIntegerExampleNaturalLanguageAPI("XPRESS") def RunAllIntegerExampleCppStyleAPI(): @@ -110,6 +111,7 @@ def RunAllIntegerExampleCppStyleAPI(): # RunIntegerExampleCppStyleAPI('CBC') RunIntegerExampleCppStyleAPI("SCIP") RunIntegerExampleCppStyleAPI("SAT") + RunIntegerExampleCppStyleAPI("XPRESS") def main(): diff --git a/examples/python/linear_programming.py b/examples/python/linear_programming.py index 291ebe4f59..6b383360df 100644 --- a/examples/python/linear_programming.py +++ b/examples/python/linear_programming.py @@ -138,11 +138,13 @@ def main(): RunLinearExampleNaturalLanguageAPI("GLPK_LP") RunLinearExampleNaturalLanguageAPI("CLP") RunLinearExampleNaturalLanguageAPI("PDLP") + RunLinearExampleNaturalLanguageAPI("XPRESS_LP") RunLinearExampleCppStyleAPI("GLOP") RunLinearExampleCppStyleAPI("GLPK_LP") RunLinearExampleCppStyleAPI("CLP") RunLinearExampleCppStyleAPI("PDLP") + RunLinearExampleCppStyleAPI("XPRESS_LP") if __name__ == "__main__": diff --git a/makefiles/Makefile.cpp.mk b/makefiles/Makefile.cpp.mk index f8b73d0c38..1074477385 100644 --- a/makefiles/Makefile.cpp.mk +++ b/makefiles/Makefile.cpp.mk @@ -39,7 +39,6 @@ USE_HIGHS ?= OFF USE_PDLP := ON # OFF not supported USE_SCIP ?= ON USE_CPLEX ?= OFF -USE_XPRESS ?= OFF USE_DOTNET_CORE_31 ?= OFF USE_DOTNET_6 ?= ON @@ -72,7 +71,6 @@ third_party: -DUSE_PDLP=$(USE_PDLP) \ -DUSE_SCIP=$(USE_SCIP) \ -DUSE_CPLEX=$(USE_CPLEX) \ - -DUSE_XPRESS=$(USE_XPRESS) \ -DUSE_DOTNET_CORE_31=$(USE_DOTNET_CORE_31) \ -DUSE_DOTNET_6=$(USE_DOTNET_6) \ -DBUILD_VENV=$(BUILD_VENV) \ @@ -665,7 +663,6 @@ detect_cpp: @echo USE_SCIP = $(USE_SCIP) @echo USE_GLPK = $(USE_GLPK) @echo USE_CPLEX = $(USE_CPLEX) - @echo USE_XPRESS = $(USE_XPRESS) ifdef GLPK_ROOT @echo GLPK_ROOT = $(GLPK_ROOT) endif diff --git a/ortools/linear_solver/BUILD.bazel b/ortools/linear_solver/BUILD.bazel index d328cbfb2c..7156a4c939 100644 --- a/ortools/linear_solver/BUILD.bazel +++ b/ortools/linear_solver/BUILD.bazel @@ -143,18 +143,6 @@ config_setting( }, ) -bool_flag( - name = "with_xpress", - build_setting_default = False, -) - -config_setting( - name = "use_xpress", - flag_values = { - ":with_xpress": "true", - }, -) - proto_library( name = "linear_solver_proto", srcs = ["linear_solver.proto"], @@ -187,6 +175,7 @@ cc_library( "linear_solver_callback.cc", "lpi_glop.cpp", "sat_interface.cc", + "xpress_interface.cc", ] + select({ ":use_bop": ["bop_interface.cc"], "//conditions:default": [], @@ -220,9 +209,6 @@ cc_library( }) + select({ ":use_cplex": ["cplex_interface.cc"], "//conditions:default": [], - }) + select({ - ":use_xpress": ["xpress_interface.cc"], - "//conditions:default": [], }), hdrs = [ "linear_expr.h", @@ -261,9 +247,6 @@ cc_library( }) + select({ ":use_cplex": ["-DUSE_CPLEX"], "//conditions:default": [], - }) + select({ - ":use_xpress": ["-DUSE_XPRESS"], - "//conditions:default": [], }), deps = [ ":linear_solver_cc_proto", @@ -279,6 +262,7 @@ cc_library( "//ortools/base:stl_util", "//ortools/base:timer", "//ortools/gurobi:environment", + "//ortools/xpress:environment", "//ortools/gurobi:gurobi_util", "//ortools/linear_solver/proto_solver:glop_proto_solver", "//ortools/linear_solver/proto_solver:gurobi_proto_solver", diff --git a/ortools/linear_solver/CMakeLists.txt b/ortools/linear_solver/CMakeLists.txt index bdea358e66..6a3ccfdeb5 100644 --- a/ortools/linear_solver/CMakeLists.txt +++ b/ortools/linear_solver/CMakeLists.txt @@ -16,6 +16,7 @@ list(REMOVE_ITEM _SRCS ${CMAKE_CURRENT_SOURCE_DIR}/solve.cc ) list(FILTER _SRCS EXCLUDE REGEX "/model_exporter_main\\.cc") +list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc") if(USE_SCIP) list(APPEND _SRCS ${LPI_GLOP_SRC}) endif() @@ -44,7 +45,6 @@ target_link_libraries(${NAME} PRIVATE $<$:HIGHS::HIGHS> $<$:Eigen3::Eigen> $<$:libscip> - $<$:XPRESS::XPRESS> ${PROJECT_NAMESPACE}::${PROJECT_NAME}_proto) #add_library(${PROJECT_NAMESPACE}::linear_solver ALIAS ${NAME}) @@ -68,3 +68,18 @@ elseif(UNIX) endif() install(TARGETS solve) + +if (BUILD_CXX_EXAMPLES) + if (APPLE) + set(CMAKE_INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif (UNIX) + set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN:$ORIGIN/../lib:$ORIGIN") + endif () + + add_executable(test_xprs_interface xpress_interface_test.cc) + target_compile_features(test_xprs_interface PRIVATE cxx_std_17) + target_link_libraries(test_xprs_interface PRIVATE ortools::ortools GTest::gtest_main) + + add_test(NAME cxx_unittests_xpress_interface COMMAND test_xprs_interface) +endif () diff --git a/ortools/linear_solver/java/linear_solver.i b/ortools/linear_solver/java/linear_solver.i index 0afd3114de..43dd1b78aa 100644 --- a/ortools/linear_solver/java/linear_solver.i +++ b/ortools/linear_solver/java/linear_solver.i @@ -390,6 +390,7 @@ PROTO2_RETURN( %rename (suppressOutput) operations_research::MPSolver::SuppressOutput; // no test %rename (lookupConstraintOrNull) operations_research::MPSolver::LookupConstraintOrNull; // no test %rename (lookupVariableOrNull) operations_research::MPSolver::LookupVariableOrNull; // no test +%rename (write) operations_research::MPSolver::Write; // Expose very advanced parts of the MPSolver API. For expert users only. %rename (computeConstraintActivities) operations_research::MPSolver::ComputeConstraintActivities; diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index d4eb2963db..e00b640fd2 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -403,10 +403,9 @@ extern MPSolverInterface* BuildGurobiInterface(bool mip, #if defined(USE_CPLEX) extern MPSolverInterface* BuildCplexInterface(bool mip, MPSolver* const solver); #endif -#if defined(USE_XPRESS) extern MPSolverInterface* BuildXpressInterface(bool mip, MPSolver* const solver); -#endif + namespace { MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { @@ -460,12 +459,10 @@ MPSolverInterface* BuildSolverInterface(MPSolver* const solver) { case MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING: return BuildCplexInterface(true, solver); #endif -#if defined(USE_XPRESS) case MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING: return BuildXpressInterface(true, solver); case MPSolver::XPRESS_LINEAR_PROGRAMMING: return BuildXpressInterface(false, solver); -#endif default: // TODO(user): Revert to the best *available* interface. LOG(FATAL) << "Linear solver not recognized."; @@ -501,6 +498,7 @@ MPSolver::MPSolver(const std::string& name, MPSolver::~MPSolver() { Clear(); } extern bool GurobiIsCorrectlyInstalled(); +extern bool XpressIsCorrectlyInstalled(); // static bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { @@ -545,12 +543,10 @@ bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { return true; } #endif -#ifdef USE_XPRESS if (problem_type == XPRESS_MIXED_INTEGER_PROGRAMMING || problem_type == XPRESS_LINEAR_PROGRAMMING) { - return true; + return XpressIsCorrectlyInstalled(); } -#endif return false; } diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index eb32fcc9e3..625ffe17a6 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -1661,7 +1661,7 @@ class MPSolverInterface { } // Writes the model using the solver internal write function. Currently only - // available for GurobiInterface. + // available for GurobiInterface and XpressInterface. virtual void Write(const std::string& filename); // ----- Model modifications and extraction ----- diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index 83a639a254..98e37728d6 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -366,6 +366,7 @@ PY_CONVERT(MPVariable); %unignore operations_research::MPSolver::NextSolution; // ExportModelAsLpFormat() is also visible: it's overridden by an %extend, above. // ExportModelAsMpsFormat() is also visible: it's overridden by an %extend, above. +%unignore operations_research::MPSolver::Write; // Expose very advanced parts of the MPSolver API. For expert users only. %unignore operations_research::MPSolver::ComputeConstraintActivities; diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index 11040b2cdf..559338c003 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -1,4 +1,4 @@ -// Copyright 2019 RTE +// Copyright 2019-2023 RTE // 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 @@ -13,60 +13,34 @@ // Initial version of this code was provided by RTE -#if defined(USE_XPRESS) - #include -#include -#include +#include #include #include #include #include -#include +#include #include #include "absl/strings/str_format.h" #include "ortools/base/logging.h" #include "ortools/base/timer.h" #include "ortools/linear_solver/linear_solver.h" - -extern "C" { -#include "xprs.h" -} +#include "ortools/xpress/environment.h" #define XPRS_INTEGER 'I' #define XPRS_CONTINUOUS 'C' -#define STRINGIFY2(X) #X -#define STRINGIFY(X) STRINGIFY2(X) -extern "C" { -static void XPRS_CC cbmessage(XPRSprob, void* cbdata, char const* msg, - int msglen, int msgtype) { - if (msgtype < 0) { - // msgtype < 0 is a request to flush all output. - LOG(INFO) << std::flush; - LOG(WARNING) << std::flush; - LOG(ERROR) << std::flush; - } else if (msglen > 0 || msg) { // Empty lines have msglen=0, msg!=NULL - switch (msgtype) { - case 1: /* info */ - LOG(INFO) << msg << std::endl; - break; - case 2: /* unused */ - break; - case 3: /* warn */ - LOG(WARNING) << msg << std::endl; - break; - case 4: /* error */ - LOG(ERROR) << msg << std::endl; - break; - } - } -} -} +// The argument to this macro is the invocation of a XPRS function that +// returns a status. If the function returns non-zero the macro aborts +// the program with an appropriate error message. +#define CHECK_STATUS(s) \ + do { \ + int const status_ = s; \ + CHECK_EQ(0, status_); \ + } while (0) -namespace { -// Get the solver version for prob as string. +namespace operations_research { std::string getSolverVersion(XPRSprob const& prob) { // XPRS_VERSION gives the version number as MAJOR*100 + RELEASE. // It does not include the build number. @@ -160,35 +134,74 @@ bool readParameter(XPRSprob const& prob, std::string const& name, return true; } -int XPRSgetnumcols(const XPRSprob& mLp) { +void printError(const XPRSprob& mLp, int line) { + char errmsg[512]; + XPRSgetlasterror(mLp, errmsg); + VLOG(0) << absl::StrFormat("Function line %d did not execute correctly: %s\n", + line, errmsg); + exit(0); +} + +void XPRS_CC XpressIntSolCallbackImpl(XPRSprob cbprob, void* cbdata); + +/**********************************************************************************\ +* Name: optimizermsg * +* Purpose: Display Optimizer error messages and warnings. * +* Arguments: const char *sMsg Message string * +* int nLen Message length * +* int nMsgLvl Message type * +* Return Value: None * +\**********************************************************************************/ +void XPRS_CC optimizermsg(XPRSprob prob, void* data, const char* sMsg, int nLen, + int nMsgLvl); + +int getnumcols(const XPRSprob& mLp) { int nCols = 0; - XPRSgetintattrib(mLp, XPRS_ORIGINALCOLS, &nCols); + XPRSgetintattrib(mLp, XPRS_COLS, &nCols); return nCols; } -int XPRSgetnumrows(const XPRSprob& mLp) { +int getnumrows(const XPRSprob& mLp) { int nRows = 0; - XPRSgetintattrib(mLp, XPRS_ORIGINALROWS, &nRows); + XPRSgetintattrib(mLp, XPRS_ROWS, &nRows); return nRows; } -int XPRSgetitcnt(const XPRSprob& mLp) { +int getitcnt(const XPRSprob& mLp) { int nIters = 0; XPRSgetintattrib(mLp, XPRS_SIMPLEXITER, &nIters); return nIters; } -int XPRSgetnodecnt(const XPRSprob& mLp) { +int getnodecnt(const XPRSprob& mLp) { int nNodes = 0; XPRSgetintattrib(mLp, XPRS_NODES, &nNodes); return nNodes; } -int XPRSsetobjoffset(const XPRSprob& mLp, double value) { - XPRSsetdblcontrol(mLp, XPRS_OBJRHS, value); +int setobjoffset(const XPRSprob& mLp, double value) { + // TODO detect xpress version + static int indexes[1] = {-1}; + double values[1] = {-value}; + XPRSchgobj(mLp, 1, indexes, values); return 0; } -} // namespace + +void addhint(const XPRSprob& mLp, int length, const double solval[], + const int colind[]) { + // The OR-Tools API does not allow setting a name for the solution + // passing NULL to XPRESS will have it generate a unique ID for the solution + if (int status = XPRSaddmipsol(mLp, length, solval, colind, NULL)) { + LOG(WARNING) << "Failed to set solution hint."; + } +} + +enum CUSTOM_INTERRUPT_REASON { CALLBACK_EXCEPTION = 0 }; + +void interruptXPRESS(XPRSprob& xprsProb, CUSTOM_INTERRUPT_REASON reason) { + // Reason values below 1000 are reserved by XPRESS + XPRSinterrupt(xprsProb, 1000 + reason); +} enum XPRS_BASIS_STATUS { XPRS_AT_LOWER = 0, @@ -203,62 +216,130 @@ enum XPRS_BASIS_STATUS { #define XPRS_NAN std::numeric_limits::quiet_NaN() #endif -// The argument to this macro is the invocation of a XPRS function that -// returns a status. If the function returns non-zero the macro aborts -// the program with an appropriate error message. -#define CHECK_STATUS(s) \ - do { \ - int const status_ = s; \ - CHECK_EQ(0, status_); \ - } while (0) - -namespace operations_research { - using std::unique_ptr; +class XpressMPCallbackContext : public MPCallbackContext { + friend class XpressInterface; + + public: + XpressMPCallbackContext(XPRSprob* xprsprob, MPCallbackEvent event, + int num_nodes) + : xprsprob_(xprsprob), + event_(event), + num_nodes_(num_nodes), + variable_values_(0){}; + + // Implementation of the interface. + MPCallbackEvent Event() override { return event_; }; + bool CanQueryVariableValues() override; + double VariableValue(const MPVariable* variable) override; + void AddCut(const LinearRange& cutting_plane) override { + LOG(WARNING) << "AddCut is not implemented yet in XPRESS interface"; + }; + void AddLazyConstraint(const LinearRange& lazy_constraint) override { + LOG(WARNING) + << "AddLazyConstraint is not implemented yet in XPRESS interface"; + }; + double SuggestSolution( + const absl::flat_hash_map& solution) override; + int64_t NumExploredNodes() override { return num_nodes_; }; + + // Call this method to update the internal state of the callback context + // before passing it to MPCallback::RunCallback(). + // Returns true if the internal state has changed. + bool UpdateFromXpressState(XPRSprob cbprob); + + private: + XPRSprob* xprsprob_; + MPCallbackEvent event_; + std::vector + variable_values_; // same order as MPVariable* elements in MPSolver + int num_nodes_; +}; + +// Wraps the MPCallback in order to catch and store exceptions +class MPCallbackWrapper { + public: + explicit MPCallbackWrapper(MPCallback* callback) : callback_(callback){}; + MPCallback* GetCallback() const { return callback_; } + // Since our (C++) call-back functions are called from the XPRESS (C) code, + // exceptions thrown in our call-back code are not caught by XPRESS. + // We have to catch them, interrupt XPRESS, and log them after XPRESS is + // effectively interrupted (ie after solve). + void CatchException(XPRSprob cbprob) { + exceptions_mutex_.lock(); + caught_exceptions_.push_back(std::current_exception()); + interruptXPRESS(cbprob, CALLBACK_EXCEPTION); + exceptions_mutex_.unlock(); + } + void LogCaughtExceptions() { + exceptions_mutex_.lock(); + for (const std::exception_ptr& ex : caught_exceptions_) { + try { + std::rethrow_exception(ex); + } catch (std::exception &ex) { + // We don't want the interface to throw exceptions, plus it causes + // SWIG issues in Java & Python. Instead, we'll only log them. + // (The use cases where the user has to raise an exception inside their + // call-back does not seem to be frequent, anyway.) + LOG(ERROR) << "Caught exception during user-defined call-back: " << ex.what(); + } + } + caught_exceptions_.clear(); + exceptions_mutex_.unlock(); + }; + + private: + MPCallback* callback_; + std::vector caught_exceptions_; + std::mutex exceptions_mutex_; +}; + // For a model that is extracted to an instance of this class there is a -// 1:1 corresponence between MPVariable instances and XPRESS columns: the +// 1:1 correspondence between MPVariable instances and XPRESS columns: the // index of an extracted variable is the column index in the XPRESS model. // Similar for instances of MPConstraint: the index of the constraint in // the model is the row index in the XPRESS model. class XpressInterface : public MPSolverInterface { public: // NOTE: 'mip' specifies the type of the problem (either continuous or - // mixed integer. This type is fixed for the lifetime of the + // mixed integer). This type is fixed for the lifetime of the // instance. There are no dynamic changes to the model type. - explicit XpressInterface(MPSolver* const solver, bool mip); - ~XpressInterface(); + explicit XpressInterface(MPSolver* solver, bool mip); + ~XpressInterface() override; // Sets the optimization direction (min/max). - virtual void SetOptimizationDirection(bool maximize); + void SetOptimizationDirection(bool maximize) override; // ----- Solve ----- // Solve the problem using the parameter values specified. - virtual MPSolver::ResultStatus Solve(MPSolverParameters const& param); + MPSolver::ResultStatus Solve(MPSolverParameters const& param) override; + + // Writes the model. + void Write(const std::string& filename) override; // ----- Model modifications and extraction ----- // Resets extracted model - virtual void Reset(); + void Reset() override; - virtual void SetVariableBounds(int var_index, double lb, double ub); - virtual void SetVariableInteger(int var_index, bool integer); - virtual void SetConstraintBounds(int row_index, double lb, double ub); + void SetVariableBounds(int var_index, double lb, double ub) override; + void SetVariableInteger(int var_index, bool integer) override; + void SetConstraintBounds(int row_index, double lb, double ub) override; - virtual void AddRowConstraint(MPConstraint* const ct); - virtual void AddVariable(MPVariable* const var); - virtual void SetCoefficient(MPConstraint* const constraint, - MPVariable const* const variable, - double new_value, double old_value); + void AddRowConstraint(MPConstraint* ct) override; + void AddVariable(MPVariable* var) override; + void SetCoefficient(MPConstraint* constraint, MPVariable const* variable, + double new_value, double old_value) override; // Clear a constraint from all its terms. - virtual void ClearConstraint(MPConstraint* const constraint); + void ClearConstraint(MPConstraint* constraint) override; // Change a coefficient in the linear objective - virtual void SetObjectiveCoefficient(MPVariable const* const variable, - double coefficient); + void SetObjectiveCoefficient(MPVariable const* variable, + double coefficient) override; // Change the constant term in the linear objective. - virtual void SetObjectiveOffset(double value); + void SetObjectiveOffset(double value) override; // Clear the objective from all its terms. - virtual void ClearObjective(); + void ClearObjective() override; // ------ Query statistics on the solution and the solve ------ // Number of simplex iterations @@ -267,9 +348,9 @@ class XpressInterface : public MPSolverInterface { virtual int64_t nodes() const; // Returns the basis status of a row. - virtual MPSolver::BasisStatus row_status(int constraint_index) const; + MPSolver::BasisStatus row_status(int constraint_index) const override; // Returns the basis status of a column. - virtual MPSolver::BasisStatus column_status(int variable_index) const; + MPSolver::BasisStatus column_status(int variable_index) const override; // ----- Misc ----- @@ -280,6 +361,10 @@ class XpressInterface : public MPSolverInterface { bool IsLP() const override { return !mMip; } bool IsMIP() const override { return mMip; } + void SetStartingLpBasis( + const std::vector& variable_statuses, + const std::vector& constraint_statuses) override; + void ExtractNewVariables() override; void ExtractNewConstraints() override; void ExtractObjective() override; @@ -301,8 +386,9 @@ class XpressInterface : public MPSolverInterface { return 0.0; } - bool SetSolverSpecificParametersAsString( - const std::string& parameters) override; + void SetCallback(MPCallback* mp_callback) override; + bool SupportsCallbacks() const override { return true; } + bool InterruptSolve() override { if (mLp) XPRSinterrupt(mLp, XPRS_STOP_USER); return true; @@ -327,13 +413,14 @@ class XpressInterface : public MPSolverInterface { // solution information as well. It is the counterpart of // MPSolverInterface::InvalidateSolutionSynchronization void InvalidateModelSynchronization() { - mCstat = 0; - mRstat = 0; + mCstat.clear(); + mRstat.clear(); sync_status_ = MUST_RELOAD; } - - // Transform XPRESS basis status to MPSolver basis status. - static MPSolver::BasisStatus xformBasisStatus(int xpress_basis_status); + // Adds a new feasible, infeasible or partial MIP solution for the problem to + // the Optimizer. The hint is read in the MPSolver where the user set it using + // SetHint() + void AddSolutionHintToOptimizer(); bool readParameters(std::istream& is, char sep); @@ -369,131 +456,402 @@ class XpressInterface : public MPSolverInterface { SlowUpdatesAll = 0xffff } const slowUpdates; // XPRESS has no method to query the basis status of a single variable. - // Hence we query the status only once and cache the array. This is + // Hence, we query the status only once and cache the array. This is // much faster in case the basis status of more than one row/column // is required. - unique_ptr mutable mCstat; - unique_ptr mutable mRstat; - // Setup the right-hand side of a constraint from its lower and upper bound. + // TODO + std::vector mutable mCstat; + std::vector mutable mRstat; + + std::vector mutable initial_variables_basis_status_; + std::vector mutable initial_constraint_basis_status_; + + // Set up the right-hand side of a constraint from its lower and upper bound. static void MakeRhs(double lb, double ub, double& rhs, char& sense, double& range); + + std::map& mapStringControls_; + std::map& mapDoubleControls_; + std::map& mapIntegerControls_; + std::map& mapInteger64Controls_; + + bool SetSolverSpecificParametersAsString( + const std::string& parameters) override; + MPCallback* callback_ = nullptr; }; -/** init XPRESS environment */ -int init_xpress_env(int xpress_oem_license_key = 0) { - int code; +// Transform MPSolver basis status to XPRESS status +static int MPSolverToXpressBasisStatus( + MPSolver::BasisStatus mpsolver_basis_status); +// Transform XPRESS basis status to MPSolver basis status. +static MPSolver::BasisStatus XpressToMPSolverBasisStatus( + int xpress_basis_status); - const char* xpress_from_env = getenv("XPRESS"); - std::string xpresspath; - - if (xpress_from_env == nullptr) { -#if defined(XPRESS_PATH) - std::string path(STRINGIFY(XPRESS_PATH)); - LOG(WARNING) - << "Environment variable XPRESS undefined. Trying compile path " - << "'" << path << "'"; -#if defined(_MSC_VER) - // need to remove the enclosing '\"' from the string itself. - path.erase(std::remove(path.begin(), path.end(), '\"'), path.end()); - xpresspath = path + "\\bin"; -#else // _MSC_VER - xpresspath = path + "/bin"; -#endif // _MSC_VER -#else - LOG(WARNING) - << "XpressInterface Error : Environment variable XPRESS undefined.\n"; - return -1; -#endif - } else { - xpresspath = xpress_from_env; - } - - /** if not an OEM key */ - if (xpress_oem_license_key == 0) { - LOG(WARNING) << "XpressInterface : Initialising xpress-MP with parameter " - << xpresspath << std::endl; - - code = XPRSinit(xpresspath.c_str()); - - if (!code) { - /** XPRSbanner informs about Xpress version, options and error messages */ - char banner[1000]; - XPRSgetbanner(banner); - - LOG(WARNING) << "XpressInterface : Xpress banner :\n" - << banner << std::endl; - return 0; - } else { - char errmsg[256]; - XPRSgetlicerrmsg(errmsg, 256); - - VLOG(0) << "XpressInterface : License error : " << errmsg << std::endl; - VLOG(0) << "XpressInterface : XPRSinit returned code : " << code << "\n"; - - char banner[1000]; - XPRSgetbanner(banner); - - LOG(ERROR) << "XpressInterface : Xpress banner :\n" << banner << "\n"; - return -1; - } - } else { - /** if OEM key */ - LOG(WARNING) << "XpressInterface : Initialising xpress-MP with OEM key " - << xpress_oem_license_key << "\n"; - - int nvalue = 0; - int ierr; - char slicmsg[256] = ""; - char errmsg[256]; - - XPRSlicense(&nvalue, slicmsg); - VLOG(0) << "XpressInterface : First message from XPRSLicense : " << slicmsg - << "\n"; - - nvalue = xpress_oem_license_key - ((nvalue * nvalue) / 19); - ierr = XPRSlicense(&nvalue, slicmsg); - - VLOG(0) << "XpressInterface : Second message from XPRSLicense : " << slicmsg - << "\n"; - if (ierr == 16) { - VLOG(0) << "XpressInterface : Optimizer development software detected\n"; - } else if (ierr != 0) { - /** get the license error message */ - XPRSgetlicerrmsg(errmsg, 256); - - LOG(ERROR) << "XpressInterface : " << errmsg << "\n"; - return -1; - } - - code = XPRSinit(NULL); - - if (!code) { - return 0; - } else { - LOG(ERROR) << "XPRSinit returned code : " << code << "\n"; - return -1; - } - } +static std::map& getMapStringControls() { + static std::map mapControls = { + {"MPSRHSNAME", XPRS_MPSRHSNAME}, + {"MPSOBJNAME", XPRS_MPSOBJNAME}, + {"MPSRANGENAME", XPRS_MPSRANGENAME}, + {"MPSBOUNDNAME", XPRS_MPSBOUNDNAME}, + {"OUTPUTMASK", XPRS_OUTPUTMASK}, + {"TUNERMETHODFILE", XPRS_TUNERMETHODFILE}, + {"TUNEROUTPUTPATH", XPRS_TUNEROUTPUTPATH}, + {"TUNERSESSIONNAME", XPRS_TUNERSESSIONNAME}, + {"COMPUTEEXECSERVICE", XPRS_COMPUTEEXECSERVICE}, + }; + return mapControls; } -// Creates a LP/MIP instance. +static std::map& getMapDoubleControls() { + static std::map mapControls = { + {"MAXCUTTIME", XPRS_MAXCUTTIME}, + {"MAXSTALLTIME", XPRS_MAXSTALLTIME}, + {"TUNERMAXTIME", XPRS_TUNERMAXTIME}, + {"MATRIXTOL", XPRS_MATRIXTOL}, + {"PIVOTTOL", XPRS_PIVOTTOL}, + {"FEASTOL", XPRS_FEASTOL}, + {"OUTPUTTOL", XPRS_OUTPUTTOL}, + {"SOSREFTOL", XPRS_SOSREFTOL}, + {"OPTIMALITYTOL", XPRS_OPTIMALITYTOL}, + {"ETATOL", XPRS_ETATOL}, + {"RELPIVOTTOL", XPRS_RELPIVOTTOL}, + {"MIPTOL", XPRS_MIPTOL}, + {"MIPTOLTARGET", XPRS_MIPTOLTARGET}, + {"BARPERTURB", XPRS_BARPERTURB}, + {"MIPADDCUTOFF", XPRS_MIPADDCUTOFF}, + {"MIPABSCUTOFF", XPRS_MIPABSCUTOFF}, + {"MIPRELCUTOFF", XPRS_MIPRELCUTOFF}, + {"PSEUDOCOST", XPRS_PSEUDOCOST}, + {"PENALTY", XPRS_PENALTY}, + {"BIGM", XPRS_BIGM}, + {"MIPABSSTOP", XPRS_MIPABSSTOP}, + {"MIPRELSTOP", XPRS_MIPRELSTOP}, + {"CROSSOVERACCURACYTOL", XPRS_CROSSOVERACCURACYTOL}, + {"PRIMALPERTURB", XPRS_PRIMALPERTURB}, + {"DUALPERTURB", XPRS_DUALPERTURB}, + {"BAROBJSCALE", XPRS_BAROBJSCALE}, + {"BARRHSSCALE", XPRS_BARRHSSCALE}, + {"CHOLESKYTOL", XPRS_CHOLESKYTOL}, + {"BARGAPSTOP", XPRS_BARGAPSTOP}, + {"BARDUALSTOP", XPRS_BARDUALSTOP}, + {"BARPRIMALSTOP", XPRS_BARPRIMALSTOP}, + {"BARSTEPSTOP", XPRS_BARSTEPSTOP}, + {"ELIMTOL", XPRS_ELIMTOL}, + {"MARKOWITZTOL", XPRS_MARKOWITZTOL}, + {"MIPABSGAPNOTIFY", XPRS_MIPABSGAPNOTIFY}, + {"MIPRELGAPNOTIFY", XPRS_MIPRELGAPNOTIFY}, + {"BARLARGEBOUND", XPRS_BARLARGEBOUND}, + {"PPFACTOR", XPRS_PPFACTOR}, + {"REPAIRINDEFINITEQMAX", XPRS_REPAIRINDEFINITEQMAX}, + {"BARGAPTARGET", XPRS_BARGAPTARGET}, + {"DUMMYCONTROL", XPRS_DUMMYCONTROL}, + {"BARSTARTWEIGHT", XPRS_BARSTARTWEIGHT}, + {"BARFREESCALE", XPRS_BARFREESCALE}, + {"SBEFFORT", XPRS_SBEFFORT}, + {"HEURDIVERANDOMIZE", XPRS_HEURDIVERANDOMIZE}, + {"HEURSEARCHEFFORT", XPRS_HEURSEARCHEFFORT}, + {"CUTFACTOR", XPRS_CUTFACTOR}, + {"EIGENVALUETOL", XPRS_EIGENVALUETOL}, + {"INDLINBIGM", XPRS_INDLINBIGM}, + {"TREEMEMORYSAVINGTARGET", XPRS_TREEMEMORYSAVINGTARGET}, + {"INDPRELINBIGM", XPRS_INDPRELINBIGM}, + {"RELAXTREEMEMORYLIMIT", XPRS_RELAXTREEMEMORYLIMIT}, + {"MIPABSGAPNOTIFYOBJ", XPRS_MIPABSGAPNOTIFYOBJ}, + {"MIPABSGAPNOTIFYBOUND", XPRS_MIPABSGAPNOTIFYBOUND}, + {"PRESOLVEMAXGROW", XPRS_PRESOLVEMAXGROW}, + {"HEURSEARCHTARGETSIZE", XPRS_HEURSEARCHTARGETSIZE}, + {"CROSSOVERRELPIVOTTOL", XPRS_CROSSOVERRELPIVOTTOL}, + {"CROSSOVERRELPIVOTTOLSAFE", XPRS_CROSSOVERRELPIVOTTOLSAFE}, + {"DETLOGFREQ", XPRS_DETLOGFREQ}, + {"MAXIMPLIEDBOUND", XPRS_MAXIMPLIEDBOUND}, + {"FEASTOLTARGET", XPRS_FEASTOLTARGET}, + {"OPTIMALITYTOLTARGET", XPRS_OPTIMALITYTOLTARGET}, + {"PRECOMPONENTSEFFORT", XPRS_PRECOMPONENTSEFFORT}, + {"LPLOGDELAY", XPRS_LPLOGDELAY}, + {"HEURDIVEITERLIMIT", XPRS_HEURDIVEITERLIMIT}, + {"BARKERNEL", XPRS_BARKERNEL}, + {"FEASTOLPERTURB", XPRS_FEASTOLPERTURB}, + {"CROSSOVERFEASWEIGHT", XPRS_CROSSOVERFEASWEIGHT}, + {"LUPIVOTTOL", XPRS_LUPIVOTTOL}, + {"MIPRESTARTGAPTHRESHOLD", XPRS_MIPRESTARTGAPTHRESHOLD}, + {"NODEPROBINGEFFORT", XPRS_NODEPROBINGEFFORT}, + {"INPUTTOL", XPRS_INPUTTOL}, + {"MIPRESTARTFACTOR", XPRS_MIPRESTARTFACTOR}, + {"BAROBJPERTURB", XPRS_BAROBJPERTURB}, + {"CPIALPHA", XPRS_CPIALPHA}, + {"GLOBALBOUNDINGBOX", XPRS_GLOBALBOUNDINGBOX}, + {"TIMELIMIT", XPRS_TIMELIMIT}, + {"SOLTIMELIMIT", XPRS_SOLTIMELIMIT}, + {"REPAIRINFEASTIMELIMIT", XPRS_REPAIRINFEASTIMELIMIT}, + }; + return mapControls; +} + +static std::map& getMapIntControls() { + static std::map mapControls = { + {"EXTRAROWS", XPRS_EXTRAROWS}, + {"EXTRACOLS", XPRS_EXTRACOLS}, + {"LPITERLIMIT", XPRS_LPITERLIMIT}, + {"LPLOG", XPRS_LPLOG}, + {"SCALING", XPRS_SCALING}, + {"PRESOLVE", XPRS_PRESOLVE}, + {"CRASH", XPRS_CRASH}, + {"PRICINGALG", XPRS_PRICINGALG}, + {"INVERTFREQ", XPRS_INVERTFREQ}, + {"INVERTMIN", XPRS_INVERTMIN}, + {"MAXNODE", XPRS_MAXNODE}, + {"MAXTIME", XPRS_MAXTIME}, + {"MAXMIPSOL", XPRS_MAXMIPSOL}, + {"SIFTPASSES", XPRS_SIFTPASSES}, + {"DEFAULTALG", XPRS_DEFAULTALG}, + {"VARSELECTION", XPRS_VARSELECTION}, + {"NODESELECTION", XPRS_NODESELECTION}, + {"BACKTRACK", XPRS_BACKTRACK}, + {"MIPLOG", XPRS_MIPLOG}, + {"KEEPNROWS", XPRS_KEEPNROWS}, + {"MPSECHO", XPRS_MPSECHO}, + {"MAXPAGELINES", XPRS_MAXPAGELINES}, + {"OUTPUTLOG", XPRS_OUTPUTLOG}, + {"BARSOLUTION", XPRS_BARSOLUTION}, + {"CACHESIZE", XPRS_CACHESIZE}, + {"CROSSOVER", XPRS_CROSSOVER}, + {"BARITERLIMIT", XPRS_BARITERLIMIT}, + {"CHOLESKYALG", XPRS_CHOLESKYALG}, + {"BAROUTPUT", XPRS_BAROUTPUT}, + {"EXTRAMIPENTS", XPRS_EXTRAMIPENTS}, + {"REFACTOR", XPRS_REFACTOR}, + {"BARTHREADS", XPRS_BARTHREADS}, + {"KEEPBASIS", XPRS_KEEPBASIS}, + {"CROSSOVEROPS", XPRS_CROSSOVEROPS}, + {"VERSION", XPRS_VERSION}, + {"CROSSOVERTHREADS", XPRS_CROSSOVERTHREADS}, + {"BIGMMETHOD", XPRS_BIGMMETHOD}, + {"MPSNAMELENGTH", XPRS_MPSNAMELENGTH}, + {"ELIMFILLIN", XPRS_ELIMFILLIN}, + {"PRESOLVEOPS", XPRS_PRESOLVEOPS}, + {"MIPPRESOLVE", XPRS_MIPPRESOLVE}, + {"MIPTHREADS", XPRS_MIPTHREADS}, + {"BARORDER", XPRS_BARORDER}, + {"BREADTHFIRST", XPRS_BREADTHFIRST}, + {"AUTOPERTURB", XPRS_AUTOPERTURB}, + {"DENSECOLLIMIT", XPRS_DENSECOLLIMIT}, + {"CALLBACKFROMMASTERTHREAD", XPRS_CALLBACKFROMMASTERTHREAD}, + {"MAXMCOEFFBUFFERELEMS", XPRS_MAXMCOEFFBUFFERELEMS}, + {"REFINEOPS", XPRS_REFINEOPS}, + {"LPREFINEITERLIMIT", XPRS_LPREFINEITERLIMIT}, + {"MIPREFINEITERLIMIT", XPRS_MIPREFINEITERLIMIT}, + {"DUALIZEOPS", XPRS_DUALIZEOPS}, + {"CROSSOVERITERLIMIT", XPRS_CROSSOVERITERLIMIT}, + {"PREBASISRED", XPRS_PREBASISRED}, + {"PRESORT", XPRS_PRESORT}, + {"PREPERMUTE", XPRS_PREPERMUTE}, + {"PREPERMUTESEED", XPRS_PREPERMUTESEED}, + {"MAXMEMORYSOFT", XPRS_MAXMEMORYSOFT}, + {"CUTFREQ", XPRS_CUTFREQ}, + {"SYMSELECT", XPRS_SYMSELECT}, + {"SYMMETRY", XPRS_SYMMETRY}, + {"MAXMEMORYHARD", XPRS_MAXMEMORYHARD}, + {"MIQCPALG", XPRS_MIQCPALG}, + {"QCCUTS", XPRS_QCCUTS}, + {"QCROOTALG", XPRS_QCROOTALG}, + {"PRECONVERTSEPARABLE", XPRS_PRECONVERTSEPARABLE}, + {"ALGAFTERNETWORK", XPRS_ALGAFTERNETWORK}, + {"TRACE", XPRS_TRACE}, + {"MAXIIS", XPRS_MAXIIS}, + {"CPUTIME", XPRS_CPUTIME}, + {"COVERCUTS", XPRS_COVERCUTS}, + {"GOMCUTS", XPRS_GOMCUTS}, + {"LPFOLDING", XPRS_LPFOLDING}, + {"MPSFORMAT", XPRS_MPSFORMAT}, + {"CUTSTRATEGY", XPRS_CUTSTRATEGY}, + {"CUTDEPTH", XPRS_CUTDEPTH}, + {"TREECOVERCUTS", XPRS_TREECOVERCUTS}, + {"TREEGOMCUTS", XPRS_TREEGOMCUTS}, + {"CUTSELECT", XPRS_CUTSELECT}, + {"TREECUTSELECT", XPRS_TREECUTSELECT}, + {"DUALIZE", XPRS_DUALIZE}, + {"DUALGRADIENT", XPRS_DUALGRADIENT}, + {"SBITERLIMIT", XPRS_SBITERLIMIT}, + {"SBBEST", XPRS_SBBEST}, + {"BARINDEFLIMIT", XPRS_BARINDEFLIMIT}, + {"HEURFREQ", XPRS_HEURFREQ}, + {"HEURDEPTH", XPRS_HEURDEPTH}, + {"HEURMAXSOL", XPRS_HEURMAXSOL}, + {"HEURNODES", XPRS_HEURNODES}, + {"LNPBEST", XPRS_LNPBEST}, + {"LNPITERLIMIT", XPRS_LNPITERLIMIT}, + {"BRANCHCHOICE", XPRS_BRANCHCHOICE}, + {"BARREGULARIZE", XPRS_BARREGULARIZE}, + {"SBSELECT", XPRS_SBSELECT}, + {"LOCALCHOICE", XPRS_LOCALCHOICE}, + {"LOCALBACKTRACK", XPRS_LOCALBACKTRACK}, + {"DUALSTRATEGY", XPRS_DUALSTRATEGY}, + {"L1CACHE", XPRS_L1CACHE}, + {"HEURDIVESTRATEGY", XPRS_HEURDIVESTRATEGY}, + {"HEURSELECT", XPRS_HEURSELECT}, + {"BARSTART", XPRS_BARSTART}, + {"PRESOLVEPASSES", XPRS_PRESOLVEPASSES}, + {"BARNUMSTABILITY", XPRS_BARNUMSTABILITY}, + {"BARORDERTHREADS", XPRS_BARORDERTHREADS}, + {"EXTRASETS", XPRS_EXTRASETS}, + {"FEASIBILITYPUMP", XPRS_FEASIBILITYPUMP}, + {"PRECOEFELIM", XPRS_PRECOEFELIM}, + {"PREDOMCOL", XPRS_PREDOMCOL}, + {"HEURSEARCHFREQ", XPRS_HEURSEARCHFREQ}, + {"HEURDIVESPEEDUP", XPRS_HEURDIVESPEEDUP}, + {"SBESTIMATE", XPRS_SBESTIMATE}, + {"BARCORES", XPRS_BARCORES}, + {"MAXCHECKSONMAXTIME", XPRS_MAXCHECKSONMAXTIME}, + {"MAXCHECKSONMAXCUTTIME", XPRS_MAXCHECKSONMAXCUTTIME}, + {"HISTORYCOSTS", XPRS_HISTORYCOSTS}, + {"ALGAFTERCROSSOVER", XPRS_ALGAFTERCROSSOVER}, + {"MUTEXCALLBACKS", XPRS_MUTEXCALLBACKS}, + {"BARCRASH", XPRS_BARCRASH}, + {"HEURDIVESOFTROUNDING", XPRS_HEURDIVESOFTROUNDING}, + {"HEURSEARCHROOTSELECT", XPRS_HEURSEARCHROOTSELECT}, + {"HEURSEARCHTREESELECT", XPRS_HEURSEARCHTREESELECT}, + {"MPS18COMPATIBLE", XPRS_MPS18COMPATIBLE}, + {"ROOTPRESOLVE", XPRS_ROOTPRESOLVE}, + {"CROSSOVERDRP", XPRS_CROSSOVERDRP}, + {"FORCEOUTPUT", XPRS_FORCEOUTPUT}, + {"PRIMALOPS", XPRS_PRIMALOPS}, + {"DETERMINISTIC", XPRS_DETERMINISTIC}, + {"PREPROBING", XPRS_PREPROBING}, + {"TREEMEMORYLIMIT", XPRS_TREEMEMORYLIMIT}, + {"TREECOMPRESSION", XPRS_TREECOMPRESSION}, + {"TREEDIAGNOSTICS", XPRS_TREEDIAGNOSTICS}, + {"MAXTREEFILESIZE", XPRS_MAXTREEFILESIZE}, + {"PRECLIQUESTRATEGY", XPRS_PRECLIQUESTRATEGY}, + {"REPAIRINFEASMAXTIME", XPRS_REPAIRINFEASMAXTIME}, + {"IFCHECKCONVEXITY", XPRS_IFCHECKCONVEXITY}, + {"PRIMALUNSHIFT", XPRS_PRIMALUNSHIFT}, + {"REPAIRINDEFINITEQ", XPRS_REPAIRINDEFINITEQ}, + {"MIPRAMPUP", XPRS_MIPRAMPUP}, + {"MAXLOCALBACKTRACK", XPRS_MAXLOCALBACKTRACK}, + {"USERSOLHEURISTIC", XPRS_USERSOLHEURISTIC}, + {"FORCEPARALLELDUAL", XPRS_FORCEPARALLELDUAL}, + {"BACKTRACKTIE", XPRS_BACKTRACKTIE}, + {"BRANCHDISJ", XPRS_BRANCHDISJ}, + {"MIPFRACREDUCE", XPRS_MIPFRACREDUCE}, + {"CONCURRENTTHREADS", XPRS_CONCURRENTTHREADS}, + {"MAXSCALEFACTOR", XPRS_MAXSCALEFACTOR}, + {"HEURTHREADS", XPRS_HEURTHREADS}, + {"THREADS", XPRS_THREADS}, + {"HEURBEFORELP", XPRS_HEURBEFORELP}, + {"PREDOMROW", XPRS_PREDOMROW}, + {"BRANCHSTRUCTURAL", XPRS_BRANCHSTRUCTURAL}, + {"QUADRATICUNSHIFT", XPRS_QUADRATICUNSHIFT}, + {"BARPRESOLVEOPS", XPRS_BARPRESOLVEOPS}, + {"QSIMPLEXOPS", XPRS_QSIMPLEXOPS}, + {"MIPRESTART", XPRS_MIPRESTART}, + {"CONFLICTCUTS", XPRS_CONFLICTCUTS}, + {"PREPROTECTDUAL", XPRS_PREPROTECTDUAL}, + {"CORESPERCPU", XPRS_CORESPERCPU}, + {"RESOURCESTRATEGY", XPRS_RESOURCESTRATEGY}, + {"CLAMPING", XPRS_CLAMPING}, + {"SLEEPONTHREADWAIT", XPRS_SLEEPONTHREADWAIT}, + {"PREDUPROW", XPRS_PREDUPROW}, + {"CPUPLATFORM", XPRS_CPUPLATFORM}, + {"BARALG", XPRS_BARALG}, + {"SIFTING", XPRS_SIFTING}, + {"LPLOGSTYLE", XPRS_LPLOGSTYLE}, + {"RANDOMSEED", XPRS_RANDOMSEED}, + {"TREEQCCUTS", XPRS_TREEQCCUTS}, + {"PRELINDEP", XPRS_PRELINDEP}, + {"DUALTHREADS", XPRS_DUALTHREADS}, + {"PREOBJCUTDETECT", XPRS_PREOBJCUTDETECT}, + {"PREBNDREDQUAD", XPRS_PREBNDREDQUAD}, + {"PREBNDREDCONE", XPRS_PREBNDREDCONE}, + {"PRECOMPONENTS", XPRS_PRECOMPONENTS}, + {"MAXMIPTASKS", XPRS_MAXMIPTASKS}, + {"MIPTERMINATIONMETHOD", XPRS_MIPTERMINATIONMETHOD}, + {"PRECONEDECOMP", XPRS_PRECONEDECOMP}, + {"HEURFORCESPECIALOBJ", XPRS_HEURFORCESPECIALOBJ}, + {"HEURSEARCHROOTCUTFREQ", XPRS_HEURSEARCHROOTCUTFREQ}, + {"PREELIMQUAD", XPRS_PREELIMQUAD}, + {"PREIMPLICATIONS", XPRS_PREIMPLICATIONS}, + {"TUNERMODE", XPRS_TUNERMODE}, + {"TUNERMETHOD", XPRS_TUNERMETHOD}, + {"TUNERTARGET", XPRS_TUNERTARGET}, + {"TUNERTHREADS", XPRS_TUNERTHREADS}, + {"TUNERHISTORY", XPRS_TUNERHISTORY}, + {"TUNERPERMUTE", XPRS_TUNERPERMUTE}, + {"TUNERVERBOSE", XPRS_TUNERVERBOSE}, + {"TUNEROUTPUT", XPRS_TUNEROUTPUT}, + {"PREANALYTICCENTER", XPRS_PREANALYTICCENTER}, + {"NETCUTS", XPRS_NETCUTS}, + {"LPFLAGS", XPRS_LPFLAGS}, + {"MIPKAPPAFREQ", XPRS_MIPKAPPAFREQ}, + {"OBJSCALEFACTOR", XPRS_OBJSCALEFACTOR}, + {"TREEFILELOGINTERVAL", XPRS_TREEFILELOGINTERVAL}, + {"IGNORECONTAINERCPULIMIT", XPRS_IGNORECONTAINERCPULIMIT}, + {"IGNORECONTAINERMEMORYLIMIT", XPRS_IGNORECONTAINERMEMORYLIMIT}, + {"MIPDUALREDUCTIONS", XPRS_MIPDUALREDUCTIONS}, + {"GENCONSDUALREDUCTIONS", XPRS_GENCONSDUALREDUCTIONS}, + {"PWLDUALREDUCTIONS", XPRS_PWLDUALREDUCTIONS}, + {"BARFAILITERLIMIT", XPRS_BARFAILITERLIMIT}, + {"AUTOSCALING", XPRS_AUTOSCALING}, + {"GENCONSABSTRANSFORMATION", XPRS_GENCONSABSTRANSFORMATION}, + {"COMPUTEJOBPRIORITY", XPRS_COMPUTEJOBPRIORITY}, + {"PREFOLDING", XPRS_PREFOLDING}, + {"NETSTALLLIMIT", XPRS_NETSTALLLIMIT}, + {"SERIALIZEPREINTSOL", XPRS_SERIALIZEPREINTSOL}, + {"NUMERICALEMPHASIS", XPRS_NUMERICALEMPHASIS}, + {"PWLNONCONVEXTRANSFORMATION", XPRS_PWLNONCONVEXTRANSFORMATION}, + {"MIPCOMPONENTS", XPRS_MIPCOMPONENTS}, + {"MIPCONCURRENTNODES", XPRS_MIPCONCURRENTNODES}, + {"MIPCONCURRENTSOLVES", XPRS_MIPCONCURRENTSOLVES}, + {"OUTPUTCONTROLS", XPRS_OUTPUTCONTROLS}, + {"SIFTSWITCH", XPRS_SIFTSWITCH}, + {"HEUREMPHASIS", XPRS_HEUREMPHASIS}, + {"COMPUTEMATX", XPRS_COMPUTEMATX}, + {"COMPUTEMATX_IIS", XPRS_COMPUTEMATX_IIS}, + {"COMPUTEMATX_IISMAXTIME", XPRS_COMPUTEMATX_IISMAXTIME}, + {"BARREFITER", XPRS_BARREFITER}, + {"COMPUTELOG", XPRS_COMPUTELOG}, + {"SIFTPRESOLVEOPS", XPRS_SIFTPRESOLVEOPS}, + {"CHECKINPUTDATA", XPRS_CHECKINPUTDATA}, + {"ESCAPENAMES", XPRS_ESCAPENAMES}, + {"IOTIMEOUT", XPRS_IOTIMEOUT}, + {"AUTOCUTTING", XPRS_AUTOCUTTING}, + {"CALLBACKCHECKTIMEDELAY", XPRS_CALLBACKCHECKTIMEDELAY}, + {"MULTIOBJOPS", XPRS_MULTIOBJOPS}, + {"MULTIOBJLOG", XPRS_MULTIOBJLOG}, + {"GLOBALSPATIALBRANCHIFPREFERORIG", XPRS_GLOBALSPATIALBRANCHIFPREFERORIG}, + {"PRECONFIGURATION", XPRS_PRECONFIGURATION}, + {"FEASIBILITYJUMP", XPRS_FEASIBILITYJUMP}, + }; + return mapControls; +} + +static std::map& getMapInt64Controls() { + static std::map mapControls = { + {"EXTRAELEMS", XPRS_EXTRAELEMS}, + {"EXTRASETELEMS", XPRS_EXTRASETELEMS}, + }; + return mapControls; +} + +// Creates an LP/MIP instance. XpressInterface::XpressInterface(MPSolver* const solver, bool mip) : MPSolverInterface(solver), - mLp(0), + mLp(nullptr), mMip(mip), supportIncrementalExtraction(false), slowUpdates(static_cast(SlowSetObjectiveCoefficient | SlowClearObjective)), - mCstat(), - mRstat() { - int status = init_xpress_env(); - CHECK_STATUS(status); - status = XPRScreateprob(&mLp); + mapStringControls_(getMapStringControls()), + mapDoubleControls_(getMapDoubleControls()), + mapIntegerControls_(getMapIntControls()), + mapInteger64Controls_(getMapInt64Controls()) { + bool correctlyLoaded = initXpressEnv(); + CHECK(correctlyLoaded); + int status = XPRScreateprob(&mLp); CHECK_STATUS(status); DCHECK(mLp != nullptr); // should not be NULL if status=0 + int nReturn = XPRSaddcbmessage(mLp, optimizermsg, (void*)this, 0); CHECK_STATUS(XPRSloadlp(mLp, "newProb", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); - CHECK_STATUS( XPRSchgobjsense(mLp, maximize_ ? XPRS_OBJ_MAXIMIZE : XPRS_OBJ_MINIMIZE)); } @@ -504,7 +862,21 @@ XpressInterface::~XpressInterface() { } std::string XpressInterface::SolverVersion() const { - return getSolverVersion(mLp); + // We prefer XPRSversionnumber() over XPRSversion() since the + // former will never pose any encoding issues. + int version = 0; + CHECK_STATUS(XPRSgetintcontrol(mLp, XPRS_VERSION, &version)); + + int const major = version / 1000000; + version -= major * 1000000; + int const release = version / 10000; + version -= release * 10000; + int const mod = version / 100; + version -= mod * 100; + int const fix = version; + + return absl::StrFormat("XPRESS library version %d.%02d.%02d.%02d", major, + release, mod, fix); } // ------ Model modifications and extraction ----- @@ -518,14 +890,15 @@ void XpressInterface::Reset() { status = XPRScreateprob(&mLp); CHECK_STATUS(status); DCHECK(mLp != nullptr); // should not be NULL if status=0 + int nReturn = XPRSaddcbmessage(mLp, optimizermsg, (void*)this, 0); CHECK_STATUS(XPRSloadlp(mLp, "newProb", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); CHECK_STATUS( XPRSchgobjsense(mLp, maximize_ ? XPRS_OBJ_MAXIMIZE : XPRS_OBJ_MINIMIZE)); ResetExtractionInformation(); - mCstat = 0; - mRstat = 0; + mCstat.clear(); + mRstat.clear(); } void XpressInterface::SetOptimizationDirection(bool maximize) { @@ -540,7 +913,7 @@ void XpressInterface::SetVariableBounds(int var_index, double lb, double ub) { // many variables may still be slow. So we don't perform the update by // default. However, if we support incremental extraction // (supportIncrementalExtraction is true) then we MUST perform the - // update here or we will lose it. + // update here, or we will lose it. if (!supportIncrementalExtraction && !(slowUpdates & SlowSetVariableBounds)) { InvalidateModelSynchronization(); @@ -575,8 +948,8 @@ void XpressInterface::SetVariableInteger(int var_index, bool integer) { // (supportIncrementalExtraction is true) then we MUST change the // type of extracted variables here. - if (!supportIncrementalExtraction && !slowUpdates && - !SlowSetVariableInteger) { + if (!supportIncrementalExtraction && + !(slowUpdates & SlowSetVariableInteger)) { InvalidateModelSynchronization(); } else { if (mMip) { @@ -584,7 +957,7 @@ void XpressInterface::SetVariableInteger(int var_index, bool integer) { // Variable is extracted. Change the type immediately. // TODO: Should we check the current type and don't do anything // in case the type does not change? - DCHECK_LE(var_index, XPRSgetnumcols(mLp)); + DCHECK_LE(var_index, getnumcols(mLp)); char const type = integer ? XPRS_INTEGER : XPRS_CONTINUOUS; CHECK_STATUS(XPRSchgcoltype(mLp, 1, &var_index, &type)); } else { @@ -597,7 +970,7 @@ void XpressInterface::SetVariableInteger(int var_index, bool integer) { } } -// Setup the right-hand side of a constraint. +// Set up the right-hand side of a constraint. void XpressInterface::MakeRhs(double lb, double ub, double& rhs, char& sense, double& range) { if (lb == ub) { @@ -612,6 +985,7 @@ void XpressInterface::MakeRhs(double lb, double ub, double& rhs, char& sense, // Xpress does not support contradictory bounds. Instead the sign on // rndval is always ignored. if (lb > ub) { + // TODO check if this is ok for the user LOG(DFATAL) << "XPRESS does not support contradictory bounds on range " "constraints! [" << lb << ", " << ub << "] will be converted to " << ub << ", " @@ -667,7 +1041,7 @@ void XpressInterface::SetConstraintBounds(int index, double lb, double ub) { if (constraint_is_extracted(index)) { // Constraint is already extracted, so we must update its bounds // and its type. - DCHECK(mLp != NULL); + DCHECK(mLp != nullptr); char sense; double range, rhs; MakeRhs(lb, ub, rhs, sense, range); @@ -697,16 +1071,26 @@ void XpressInterface::AddRowConstraint(MPConstraint* const ct) { // constraint. We could immediately call XPRSaddrows() here but it is // usually much faster to handle the fully populated constraint in // ExtractNewConstraints() right before the solve. + + // TODO + // Make new constraints basic (rowstat[jrow]=1) + // Try not to delete basic variables, or non-basic constraints. InvalidateModelSynchronization(); } -void XpressInterface::AddVariable(MPVariable* const ct) { +void XpressInterface::AddVariable(MPVariable* const var) { // This is currently only invoked when a new variable is created, // see MPSolver::MakeVar(). // At this point the variable does not appear in any constraints or // the objective function. We could invoke XPRSaddcols() to immediately - // create the variable here but it is usually much faster to handle the - // fully setup variable in ExtractNewVariables() right before the solve. + // create the variable here, but it is usually much faster to handle the + // fully set-up variable in ExtractNewVariables() right before the solve. + + // TODO + // Make new variables non-basic at their lower bound (colstat[icol]=0), unless + // a variable has an infinite lower bound and a finite upper bound, in which + // case make the variable non-basic at its upper bound (colstat[icol]=2) Try + // not to delete basic variables, or non-basic constraints. InvalidateModelSynchronization(); } @@ -720,7 +1104,7 @@ void XpressInterface::SetCoefficient(MPConstraint* const constraint, // representation. So by default we don't perform this update immediately // but instead mark the low-level modeling object "out of sync". // If we want to support incremental extraction then we MUST perform - // the modification immediately or we will lose it. + // the modification immediately, or we will lose it. if (!supportIncrementalExtraction && !(slowUpdates & SlowSetCoefficient)) { InvalidateModelSynchronization(); @@ -766,8 +1150,8 @@ void XpressInterface::ClearConstraint(MPConstraint* const constraint) { unique_ptr val(new double[len]); int j = 0; const auto& coeffs = constraint->coefficients_; - for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { - int const col = it->first->index(); + for (auto coeff : coeffs) { + int const col = coeff.first->index(); if (variable_is_extracted(col)) { rowind[j] = row; colind[j] = col; @@ -806,7 +1190,7 @@ void XpressInterface::SetObjectiveCoefficient(MPVariable const* const variable, void XpressInterface::SetObjectiveOffset(double value) { // Changing the objective offset is O(1), so we always do it immediately. InvalidateSolutionSynchronization(); - CHECK_STATUS(XPRSsetobjoffset(mLp, value)); + CHECK_STATUS(setobjoffset(mLp, value)); } void XpressInterface::ClearObjective() { @@ -818,13 +1202,13 @@ void XpressInterface::ClearObjective() { // but to perform the update immediately. if (supportIncrementalExtraction || (slowUpdates & SlowClearObjective)) { - int const cols = XPRSgetnumcols(mLp); + int const cols = getnumcols(mLp); unique_ptr ind(new int[cols]); unique_ptr zero(new double[cols]); int j = 0; const auto& coeffs = solver_->objective_->coefficients_; - for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { - int const idx = it->first->index(); + for (auto coeff : coeffs) { + int const idx = coeff.first->index(); // We only need to reset variables that have been extracted. if (variable_is_extracted(idx)) { DCHECK_LT(idx, cols); @@ -833,8 +1217,10 @@ void XpressInterface::ClearObjective() { ++j; } } - if (j > 0) CHECK_STATUS(XPRSchgobj(mLp, j, ind.get(), zero.get())); - CHECK_STATUS(XPRSsetobjoffset(mLp, 0.0)); + if (j > 0) { + CHECK_STATUS(XPRSchgobj(mLp, j, ind.get(), zero.get())); + } + CHECK_STATUS(setobjoffset(mLp, 0.0)); } else { InvalidateModelSynchronization(); } @@ -844,13 +1230,13 @@ void XpressInterface::ClearObjective() { int64_t XpressInterface::iterations() const { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfIterations; - return static_cast(XPRSgetitcnt(mLp)); + return static_cast(getitcnt(mLp)); } int64_t XpressInterface::nodes() const { if (mMip) { if (!CheckSolutionIsSynchronized()) return kUnknownNumberOfNodes; - return static_cast(XPRSgetnodecnt(mLp)); + return static_cast(getnodecnt(mLp)); } else { LOG(DFATAL) << "Number of nodes only available for discrete problems"; return kUnknownNumberOfNodes; @@ -858,7 +1244,7 @@ int64_t XpressInterface::nodes() const { } // Transform a XPRESS basis status to an MPSolver basis status. -MPSolver::BasisStatus XpressInterface::xformBasisStatus( +static MPSolver::BasisStatus XpressToMPSolverBasisStatus( int xpress_basis_status) { switch (xpress_basis_status) { case XPRS_AT_LOWER: @@ -875,6 +1261,24 @@ MPSolver::BasisStatus XpressInterface::xformBasisStatus( } } +static int MPSolverToXpressBasisStatus(MPSolver::BasisStatus mpsolver_basis_status) { + switch (mpsolver_basis_status) { + case MPSolver::AT_LOWER_BOUND: + return XPRS_AT_LOWER; + case MPSolver::BASIC: + return XPRS_BASIC; + case MPSolver::AT_UPPER_BOUND: + return XPRS_AT_UPPER; + case MPSolver::FREE: + return XPRS_FREE_SUPER; + case MPSolver::FIXED_VALUE: + return XPRS_BASIC; + default: + LOG(DFATAL) << "Unknown MPSolver basis status"; + return XPRS_FREE_SUPER; + } +} + // Returns the basis status of a row. MPSolver::BasisStatus XpressInterface::row_status(int constraint_index) const { if (mMip) { @@ -883,18 +1287,17 @@ MPSolver::BasisStatus XpressInterface::row_status(int constraint_index) const { } if (CheckSolutionIsSynchronized()) { - if (!mRstat) { - int const rows = XPRSgetnumrows(mLp); - unique_ptr data(new int[rows]); - mRstat.swap(data); - CHECK_STATUS(XPRSgetbasis(mLp, 0, mRstat.get())); + if (mRstat.empty()) { + int const rows = getnumrows(mLp); + mRstat.resize(rows); + CHECK_STATUS(XPRSgetbasis(mLp, mRstat.data(), 0)); } } else { - mRstat = 0; + mRstat.clear(); } - if (mRstat) { - return xformBasisStatus(mRstat[constraint_index]); + if (!mRstat.empty()) { + return XpressToMPSolverBasisStatus(mRstat[constraint_index]); } else { LOG(FATAL) << "Row basis status not available"; return MPSolver::FREE; @@ -909,18 +1312,17 @@ MPSolver::BasisStatus XpressInterface::column_status(int variable_index) const { } if (CheckSolutionIsSynchronized()) { - if (!mCstat) { - int const cols = XPRSgetnumcols(mLp); - unique_ptr data(new int[cols]); - mCstat.swap(data); - CHECK_STATUS(XPRSgetbasis(mLp, mCstat.get(), 0)); + if (mCstat.empty()) { + int const cols = getnumcols(mLp); + mCstat.resize(cols); + CHECK_STATUS(XPRSgetbasis(mLp, 0, mCstat.data())); } } else { - mCstat = 0; + mCstat.clear(); } - if (mCstat) { - return xformBasisStatus(mCstat[variable_index]); + if (!mCstat.empty()) { + return XpressToMPSolverBasisStatus(mCstat[variable_index]); } else { LOG(FATAL) << "Column basis status not available"; return MPSolver::FREE; @@ -945,28 +1347,25 @@ void XpressInterface::ExtractNewVariables() { int const last_extracted = last_variable_index_; int const var_count = solver_->variables_.size(); - int newcols = var_count - last_extracted; - if (newcols > 0) { + int new_col_count = var_count - last_extracted; + if (new_col_count > 0) { // There are non-extracted variables. Extract them now. - unique_ptr obj(new double[newcols]); - unique_ptr lb(new double[newcols]); - unique_ptr ub(new double[newcols]); - unique_ptr ctype(new char[newcols]); - unique_ptr colname(new const char*[newcols]); + unique_ptr obj(new double[new_col_count]); + unique_ptr lb(new double[new_col_count]); + unique_ptr ub(new double[new_col_count]); + unique_ptr ctype(new char[new_col_count]); - bool have_names = false; - for (int j = 0, varidx = last_extracted; j < newcols; ++j, ++varidx) { - MPVariable const* const var = solver_->variables_[varidx]; + for (int j = 0, var_idx = last_extracted; j < new_col_count; + ++j, ++var_idx) { + MPVariable const* const var = solver_->variables_[var_idx]; lb[j] = var->lb(); ub[j] = var->ub(); ctype[j] = var->integer() ? XPRS_INTEGER : XPRS_CONTINUOUS; - colname[j] = var->name().empty() ? 0 : var->name().c_str(); - have_names = have_names || var->name().empty(); obj[j] = solver_->objective_->GetCoefficient(var); } - // Arrays for modifying the problem are setup. Update the index + // Arrays for modifying the problem are set up. Update the index // of variables that will get extracted now. Updating indices // _before_ the actual extraction makes things much simpler in // case we support incremental extraction. @@ -978,7 +1377,7 @@ void XpressInterface::ExtractNewVariables() { } try { - bool use_newcols = true; + bool use_new_cols = true; if (supportIncrementalExtraction) { // If we support incremental extraction then we must @@ -989,8 +1388,8 @@ void XpressInterface::ExtractNewVariables() { // For each column count the size of the intersection with // existing constraints. - unique_ptr collen(new int[newcols]); - for (int j = 0; j < newcols; ++j) collen[j] = 0; + unique_ptr collen(new int[new_col_count]); + for (int j = 0; j < new_col_count; ++j) collen[j] = 0; int nonzeros = 0; // TODO: Use a bitarray to flag the constraints that actually // intersect new variables? @@ -998,8 +1397,8 @@ void XpressInterface::ExtractNewVariables() { MPConstraint const* const ct = solver_->constraints_[i]; CHECK(constraint_is_extracted(ct->index())); const auto& coeffs = ct->coefficients_; - for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { - int const idx = it->first->index(); + for (auto coeff : coeffs) { + int const idx = coeff.first->index(); if (variable_is_extracted(idx) && idx > last_variable_index_) { collen[idx - last_variable_index_]++; ++nonzeros; @@ -1011,75 +1410,82 @@ void XpressInterface::ExtractNewVariables() { // At least one of the new variables did intersect with an // old constraint. We have to create the new columns via // XPRSaddcols(). - use_newcols = false; - unique_ptr begin(new int[newcols + 2]); + use_new_cols = false; + unique_ptr begin(new int[new_col_count + 2]); unique_ptr cmatind(new int[nonzeros]); unique_ptr cmatval(new double[nonzeros]); - // Here is how cmatbeg[] is setup: + // Here is how cmatbeg[] is set up: // - it is initialized as // [ 0, 0, collen[0], collen[0]+collen[1], ... ] // so that cmatbeg[j+1] tells us where in cmatind[] and // cmatval[] we need to put the next nonzero for column // j - // - after nonzeros have been setup the array looks like + // - after nonzeros have been set up, the array looks like // [ 0, collen[0], collen[0]+collen[1], ... ] // so that it is the correct input argument for XPRSaddcols int* cmatbeg = begin.get(); cmatbeg[0] = 0; cmatbeg[1] = 0; ++cmatbeg; - for (int j = 0; j < newcols; ++j) + for (int j = 0; j < new_col_count; ++j) cmatbeg[j + 1] = cmatbeg[j] + collen[j]; for (int i = 0; i < last_constraint_index_; ++i) { MPConstraint const* const ct = solver_->constraints_[i]; int const row = ct->index(); const auto& coeffs = ct->coefficients_; - for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { - int const idx = it->first->index(); + for (auto coeff : coeffs) { + int const idx = coeff.first->index(); if (variable_is_extracted(idx) && idx > last_variable_index_) { int const nz = cmatbeg[idx]++; cmatind[nz] = row; - cmatval[nz] = it->second; + cmatval[nz] = coeff.second; } } } --cmatbeg; - CHECK_STATUS(XPRSaddcols(mLp, newcols, nonzeros, obj.get(), cmatbeg, - cmatind.get(), cmatval.get(), lb.get(), - ub.get())); + CHECK_STATUS(XPRSaddcols(mLp, new_col_count, nonzeros, obj.get(), + cmatbeg, cmatind.get(), cmatval.get(), + lb.get(), ub.get())); } } - if (use_newcols) { + if (use_new_cols) { // Either incremental extraction is not supported or none of // the new variables did intersect an existing constraint. // We can just use XPRSnewcols() to create the new variables. - std::vector collen(newcols, 0); - std::vector cmatbeg(newcols, 0); + std::vector collen(new_col_count, 0); + std::vector cmatbeg(new_col_count, 0); unique_ptr cmatind(new int[1]); unique_ptr cmatval(new double[1]); cmatind[0] = 0; cmatval[0] = 1.0; - CHECK_STATUS(XPRSaddcols(mLp, newcols, 0, obj.get(), cmatbeg.data(), - cmatind.get(), cmatval.get(), lb.get(), - ub.get())); - int const cols = XPRSgetnumcols(mLp); - unique_ptr ind(new int[newcols]); + CHECK_STATUS(XPRSaddcols(mLp, new_col_count, 0, obj.get(), + cmatbeg.data(), cmatind.get(), cmatval.get(), + lb.get(), ub.get())); + //TODO fixme + // Writing all names worsen the performance significantly + //if (have_names) { + // CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, col_names.data(), 0, + // new_col_count - 1)); + //} + int const cols = getnumcols(mLp); + unique_ptr ind(new int[new_col_count]); for (int j = 0; j < cols; ++j) ind[j] = j; CHECK_STATUS( XPRSchgcoltype(mLp, cols - last_extracted, ind.get(), ctype.get())); + } else { // Incremental extraction: we must update the ctype of the // newly created variables (XPRSaddcols() does not allow // specifying the ctype) - if (mMip && XPRSgetnumcols(mLp) > 0) { + if (mMip && getnumcols(mLp) > 0) { // Query the actual number of columns in case we did not // manage to extract all columns. - int const cols = XPRSgetnumcols(mLp); - unique_ptr ind(new int[newcols]); + int const cols = getnumcols(mLp); + unique_ptr ind(new int[new_col_count]); for (int j = last_extracted; j < cols; ++j) ind[j - last_extracted] = j; CHECK_STATUS(XPRSchgcoltype(mLp, cols - last_extracted, ind.get(), @@ -1088,11 +1494,11 @@ void XpressInterface::ExtractNewVariables() { } } catch (...) { // Undo all changes in case of error. - int const cols = XPRSgetnumcols(mLp); + int const cols = getnumcols(mLp); if (cols > last_extracted) { - std::vector colsToDelete; - for (int i = last_extracted; i < cols; ++i) colsToDelete.push_back(i); - (void)XPRSdelcols(mLp, colsToDelete.size(), colsToDelete.data()); + std::vector cols_to_delete; + for (int i = last_extracted; i < cols; ++i) cols_to_delete.push_back(i); + (void)XPRSdelcols(mLp, cols_to_delete.size(), cols_to_delete.data()); } std::vector const& variables = solver_->variables(); int const size = variables.size(); @@ -1125,8 +1531,7 @@ void XpressInterface::ExtractNewConstraints() { InvalidateSolutionSynchronization(); int newCons = total - offset; - int const cols = XPRSgetnumcols(mLp); - DCHECK_EQ(last_variable_index_, cols); + int const cols = getnumcols(mLp); int const chunk = newCons; // 10; // max number of rows to add in one shot // Update indices of new constraints _before_ actually extracting @@ -1139,8 +1544,9 @@ void XpressInterface::ExtractNewConstraints() { unique_ptr rmatbeg(new int[chunk]); unique_ptr sense(new char[chunk]); unique_ptr rhs(new double[chunk]); - unique_ptr name(new char const*[chunk]); unique_ptr rngval(new double[chunk]); + unique_ptr rngind(new int[chunk]); + bool haveRanges = false; // Loop over the new constraints, collecting rows for up to // CHUNK constraints into the arrays so that adding constraints @@ -1149,7 +1555,6 @@ void XpressInterface::ExtractNewConstraints() { // Collect up to CHUNK constraints into the arrays. int nextRow = 0; int nextNz = 0; - bool haveRanges = false; for (/* nothing */; c < newCons && nextRow < chunk; ++c, ++nextRow) { MPConstraint const* const ct = solver_->constraints_[offset + c]; @@ -1164,37 +1569,40 @@ void XpressInterface::ExtractNewConstraints() { MakeRhs(ct->lb(), ct->ub(), rhs[nextRow], sense[nextRow], rngval[nextRow]); haveRanges = haveRanges || (rngval[nextRow] != 0.0); + rngind[nextRow] = offset + c; // Setup left-hand side of constraint. rmatbeg[nextRow] = nextNz; const auto& coeffs = ct->coefficients_; - for (auto it(coeffs.begin()); it != coeffs.end(); ++it) { - int const idx = it->first->index(); + for (auto coeff : coeffs) { + int const idx = coeff.first->index(); if (variable_is_extracted(idx)) { DCHECK_LT(nextNz, cols); DCHECK_LT(idx, cols); rmatind[nextNz] = idx; - rmatval[nextNz] = it->second; + rmatval[nextNz] = coeff.second; ++nextNz; } } - - // Finally the name of the constraint. - name[nextRow] = ct->name().empty() ? 0 : ct->name().c_str(); } if (nextRow > 0) { CHECK_STATUS(XPRSaddrows(mLp, nextRow, nextNz, sense.get(), rhs.get(), - haveRanges ? rngval.get() : 0, rmatbeg.get(), - rmatind.get(), rmatval.get())); + rngval.get(), rmatbeg.get(), rmatind.get(), + rmatval.get())); + + if (haveRanges) { + CHECK_STATUS( + XPRSchgrhsrange(mLp, nextRow, rngind.get(), rngval.get())); + } } } } catch (...) { // Undo all changes in case of error. - int const rows = XPRSgetnumrows(mLp); - std::vector rowsToDelete; - for (int i = offset; i < rows; ++i) rowsToDelete.push_back(i); + int const rows = getnumrows(mLp); + std::vector rows_to_delete; + for (int i = offset; i < rows; ++i) rows_to_delete.push_back(i); if (rows > offset) - (void)XPRSdelrows(mLp, rowsToDelete.size(), rowsToDelete.data()); + (void)XPRSdelrows(mLp, rows_to_delete.size(), rows_to_delete.data()); std::vector const& constraints = solver_->constraints(); int const size = constraints.size(); for (int i = offset; i < size; ++i) set_constraint_as_extracted(i, false); @@ -1208,8 +1616,8 @@ void XpressInterface::ExtractObjective() { // NOTE: The code assumes that the objective expression does not contain // any non-zero duplicates. - int const cols = XPRSgetnumcols(mLp); - DCHECK_EQ(last_variable_index_, cols); + int const cols = getnumcols(mLp); + // DCHECK_EQ(last_variable_index_, cols); unique_ptr ind(new int[cols]); unique_ptr val(new double[cols]); @@ -1219,22 +1627,23 @@ void XpressInterface::ExtractObjective() { } const auto& coeffs = solver_->objective_->coefficients_; - for (auto it = coeffs.begin(); it != coeffs.end(); ++it) { - int const idx = it->first->index(); + for (auto coeff : coeffs) { + int const idx = coeff.first->index(); if (variable_is_extracted(idx)) { DCHECK_LT(idx, cols); - val[idx] = it->second; + val[idx] = coeff.second; } } CHECK_STATUS(XPRSchgobj(mLp, cols, ind.get(), val.get())); - CHECK_STATUS(XPRSsetobjoffset(mLp, solver_->Objective().offset())); + CHECK_STATUS(setobjoffset(mLp, solver_->Objective().offset())); } // ------ Parameters ----- void XpressInterface::SetParameters(const MPSolverParameters& param) { SetCommonParameters(param); + SetScalingMode(param.GetIntegerParam(MPSolverParameters::SCALING)); if (mMip) SetMIPParameters(param); } @@ -1256,8 +1665,7 @@ void XpressInterface::SetDualTolerance(double value) { } void XpressInterface::SetPresolveMode(int value) { - MPSolverParameters::PresolveValues const presolve = - static_cast(value); + auto const presolve = static_cast(value); switch (presolve) { case MPSolverParameters::PRESOLVE_OFF: @@ -1272,8 +1680,7 @@ void XpressInterface::SetPresolveMode(int value) { // Sets the scaling mode. void XpressInterface::SetScalingMode(int value) { - MPSolverParameters::ScalingValues const scaling = - static_cast(value); + auto const scaling = static_cast(value); switch (scaling) { case MPSolverParameters::SCALING_OFF: @@ -1282,9 +1689,9 @@ void XpressInterface::SetScalingMode(int value) { case MPSolverParameters::SCALING_ON: CHECK_STATUS(XPRSsetdefaultcontrol(mLp, XPRS_SCALING)); // In Xpress, scaling is not a binary on/off control, but a bit vector - // control setting it to 1 would only enable bit 1. Instead we reset it to - // its default (163 for the current version 8.6) Alternatively, we could - // call CHECK_STATUS(XPRSsetintcontrol(mLp, XPRS_SCALING, 163)); + // control setting it to 1 would only enable bit 1. Instead, we reset it + // to its default (163 for the current version 8.6) Alternatively, we + // could call CHECK_STATUS(XPRSsetintcontrol(mLp, XPRS_SCALING, 163)); break; } } @@ -1292,7 +1699,7 @@ void XpressInterface::SetScalingMode(int value) { // Sets the LP algorithm : primal, dual or barrier. Note that XPRESS offers // other LP algorithm (e.g. network) and automatic selection void XpressInterface::SetLpAlgorithm(int value) { - MPSolverParameters::LpAlgorithmValues const algorithm = + auto const algorithm = static_cast(value); int alg = 1; @@ -1315,6 +1722,25 @@ void XpressInterface::SetLpAlgorithm(int value) { CHECK_STATUS(XPRSsetintcontrol(mLp, XPRS_DEFAULTALG, alg)); } } +std::vector XpressBasisStatusesFrom( + const std::vector& statuses) { + std::vector result; + result.reserve(statuses.size()); + std::transform(statuses.cbegin(), statuses.cend(), result.begin(), + MPSolverToXpressBasisStatus); + return result; +} + +void XpressInterface::SetStartingLpBasis( + const std::vector& variable_statuses, + const std::vector& constraint_statuses){ + if (mMip) { + LOG(DFATAL) << __FUNCTION__ << " is only available for LP problems"; + return; + } + initial_variables_basis_status_ = XpressBasisStatusesFrom(variable_statuses); + initial_constraint_basis_status_ = XpressBasisStatusesFrom(constraint_statuses); +} bool XpressInterface::readParameters(std::istream& is, char sep) { // - parameters must be specified as NAME=VALUE @@ -1372,29 +1798,19 @@ std::string XpressInterface::ValidFileExtensionForParameterFile() const { return ".prm"; } -bool XpressInterface::SetSolverSpecificParametersAsString( - const std::string& parameters) { - if (parameters.empty()) { - return true; - } - std::stringstream s(parameters); - return readParameters(s, ';'); -} - MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { int status; - // Delete chached information - mCstat = 0; - mRstat = 0; + // Delete cached information + mCstat.clear(); + mRstat.clear(); WallTimer timer; timer.Start(); // Set incrementality - MPSolverParameters::IncrementalityValues const inc = - static_cast( - param.GetIntegerParam(MPSolverParameters::INCREMENTALITY)); + auto const inc = static_cast( + param.GetIntegerParam(MPSolverParameters::INCREMENTALITY)); switch (inc) { case MPSolverParameters::INCREMENTALITY_OFF: { Reset(); // This should not be required but re-extracting everything @@ -1409,14 +1825,20 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { // Extract the model to be solved. // If we don't support incremental extraction and the low-level modeling - // is out of sync then we have to re-extract everything. + // is out of sync then we have to re-extract everything. Note that this + // will lose MIP starts or advanced basis information from a previous + // solve. if (!supportIncrementalExtraction && sync_status_ == MUST_RELOAD) Reset(); ExtractModel(); VLOG(1) << absl::StrFormat("Model build in %.3f seconds.", timer.Get()); - // Enable log output. - if (!quiet()) XPRSaddcbmessage(mLp, cbmessage, nullptr, 0); + // Set log level. + XPRSsetintcontrol(mLp, XPRS_OUTPUTLOG, quiet() ? 0 : 1); // Set parameters. + // NOTE: We must invoke SetSolverSpecificParametersAsString() _first_. + // Its current implementation invokes ReadParameterFile() which in + // turn invokes XPRSreadcopyparam(). The latter will _overwrite_ + // all current parameter settings in the environment. solver_->SetSolverSpecificParametersAsString( solver_->solver_specific_parameter_string_); SetParameters(param); @@ -1425,30 +1847,54 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { // In Xpress, a time limit should usually have a negative sign. With a // positive sign, the solver will only stop when a solution has been found. CHECK_STATUS(XPRSsetintcontrol(mLp, XPRS_MAXTIME, - -1.0 * solver_->time_limit_in_secs())); + -1 * solver_->time_limit_in_secs())); + } + + // Load basis if present + // TODO : check number of variables / constraints + if (!mMip && !initial_variables_basis_status_.empty() && !initial_constraint_basis_status_.empty()) { + CHECK_STATUS(XPRSloadbasis(mLp, initial_constraint_basis_status_.data(), + initial_variables_basis_status_.data())); + } + + // Set the hint (if any) + this->AddSolutionHintToOptimizer(); + + // Add opt node callback to optimizer. We have to do this here (just before + // solve) to make sure the variables are fully initialized + MPCallbackWrapper* mp_callback_wrapper = nullptr; + if (callback_ != nullptr) { + mp_callback_wrapper = new MPCallbackWrapper(callback_); + CHECK_STATUS(XPRSaddcbintsol(mLp, XpressIntSolCallbackImpl, + static_cast(mp_callback_wrapper), 0)); } - timer.Restart(); // Solve. - // Do not CHECK_STATUS here since some errors still allow us to query useful - // information. - int xpressstat = 0; + // Do not CHECK_STATUS here since some errors (for example CPXERR_NO_MEMORY) + // still allow us to query useful information. + timer.Restart(); + + int xpress_stat = 0; if (mMip) { - if (this->maximize_) - status = XPRSmaxim(mLp, "g"); - else - status = XPRSminim(mLp, "g"); - XPRSgetintattrib(mLp, XPRS_MIPSTATUS, &xpressstat); + status = XPRSmipoptimize(mLp,""); + XPRSgetintattrib(mLp, XPRS_MIPSTATUS, &xpress_stat); } else { - if (this->maximize_) - status = XPRSmaxim(mLp, ""); - else - status = XPRSminim(mLp, ""); - XPRSgetintattrib(mLp, XPRS_LPSTATUS, &xpressstat); + status = XPRSlpoptimize(mLp,""); + XPRSgetintattrib(mLp, XPRS_LPSTATUS, &xpress_stat); + } + + if (mp_callback_wrapper != nullptr) { + mp_callback_wrapper->LogCaughtExceptions(); + delete mp_callback_wrapper; + } + + if (!(mMip ? (xpress_stat == XPRS_MIP_OPTIMAL) + : (xpress_stat == XPRS_LP_OPTIMAL))) { + XPRSpostsolve(mLp); } // Disable screen output right after solve - XPRSremovecbmessage(mLp, cbmessage, nullptr); + XPRSsetintcontrol(mLp, XPRS_OUTPUTLOG, 0); if (status) { VLOG(1) << absl::StrFormat("Failed to optimize MIP. Error %d", status); @@ -1458,16 +1904,16 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { VLOG(1) << absl::StrFormat("Solved in %.3f seconds.", timer.Get()); } - VLOG(1) << absl::StrFormat("XPRESS solution status %d.", xpressstat); + VLOG(1) << absl::StrFormat("XPRESS solution status %d.", xpress_stat); // Figure out what solution we have. - bool const feasible = (mMip && (xpressstat == XPRS_MIP_OPTIMAL || - xpressstat == XPRS_MIP_SOLUTION)) || - (!mMip && xpressstat == XPRS_LP_OPTIMAL); + bool const feasible = (mMip ? (xpress_stat == XPRS_MIP_OPTIMAL || + xpress_stat == XPRS_MIP_SOLUTION) + : (!mMip && xpress_stat == XPRS_LP_OPTIMAL)); // Get problem dimensions for solution queries below. - int const rows = XPRSgetnumrows(mLp); - int const cols = XPRSgetnumcols(mLp); + int const rows = getnumrows(mLp); + int const cols = getnumcols(mLp); DCHECK_EQ(rows, solver_->constraints_.size()); DCHECK_EQ(cols, solver_->variables_.size()); @@ -1500,15 +1946,15 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { } } } else { - for (int i = 0; i < solver_->variables_.size(); ++i) - solver_->variables_[i]->set_solution_value(XPRS_NAN); + for (auto& variable : solver_->variables_) + variable->set_solution_value(XPRS_NAN); } // MIP does not have duals - for (int i = 0; i < solver_->variables_.size(); ++i) - solver_->variables_[i]->set_reduced_cost(XPRS_NAN); - for (int i = 0; i < solver_->constraints_.size(); ++i) - solver_->constraints_[i]->set_dual_value(XPRS_NAN); + for (auto& variable : solver_->variables_) + variable->set_reduced_cost(XPRS_NAN); + for (auto& constraint : solver_->constraints_) + constraint->set_dual_value(XPRS_NAN); } else { // Continuous problem. if (cols > 0) { @@ -1560,7 +2006,7 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { // Map XPRESS status to more generic solution status in MPSolver if (mMip) { - switch (xpressstat) { + switch (xpress_stat) { case XPRS_MIP_OPTIMAL: result_status_ = MPSolver::OPTIMAL; break; @@ -1575,7 +2021,7 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { break; } } else { - switch (xpressstat) { + switch (xpress_stat) { case XPRS_LP_OPTIMAL: result_status_ = MPSolver::OPTIMAL; break; @@ -1595,9 +2041,211 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { return result_status_; } +void XpressInterface::Write(const std::string& filename) { + if (sync_status_ == MUST_RELOAD) { + Reset(); + } + ExtractModel(); + VLOG(1) << "Writing Xpress MPS \"" << filename << "\"."; + const int status = XPRSwriteprob(mLp, filename.c_str(), ""); + if (status) { + LOG(ERROR) << "Xpress: Failed to write MPS!"; + } +} + MPSolverInterface* BuildXpressInterface(bool mip, MPSolver* const solver) { return new XpressInterface(solver, mip); } +// TODO useless ? +template +void splitMyString(const std::string& str, Container& cont, char delim = ' ') { + std::stringstream ss(str); + std::string token; + while (std::getline(ss, token, delim)) { + cont.push_back(token); + } +} + +const char* stringToCharPtr(std::string& var) { return var.c_str(); } + +// Save the existing locale, use the "C" locale to ensure that +// string -> double conversion is done ignoring the locale. +struct ScopedLocale { + ScopedLocale() { + oldLocale = std::setlocale(LC_NUMERIC, nullptr); + auto newLocale = std::setlocale(LC_NUMERIC, "C"); + CHECK_EQ(std::string(newLocale), "C"); + } + ~ScopedLocale() { std::setlocale(LC_NUMERIC, oldLocale); } + + private: + const char* oldLocale; +}; + +#define setParamIfPossible_MACRO(target_map, setter, converter) \ + { \ + auto matchingParamIter = (target_map).find(paramAndValuePair.first); \ + if (matchingParamIter != (target_map).end()) { \ + const auto convertedValue = converter(paramAndValuePair.second); \ + VLOG(1) << "Setting parameter " << paramAndValuePair.first \ + << " to value " << convertedValue << std::endl; \ + setter(mLp, matchingParamIter->second, convertedValue); \ + continue; \ + } \ + } + +bool XpressInterface::SetSolverSpecificParametersAsString( + const std::string& parameters) { + if (parameters.empty()) return true; + + std::vector > paramAndValuePairList; + + std::stringstream ss(parameters); + std::string paramName; + while (std::getline(ss, paramName, ' ')) { + std::string paramValue; + if (std::getline(ss, paramValue, ' ')) { + paramAndValuePairList.push_back(std::make_pair(paramName, paramValue)); + } else { + LOG(ERROR) << "No value for parameter " << paramName << " : function " + << __FUNCTION__ << std::endl; + return false; + } + } + + ScopedLocale locale; + for (auto& paramAndValuePair : paramAndValuePairList) { + setParamIfPossible_MACRO(mapIntegerControls_, XPRSsetintcontrol, std::stoi); + setParamIfPossible_MACRO(mapDoubleControls_, XPRSsetdblcontrol, std::stod); + setParamIfPossible_MACRO(mapStringControls_, XPRSsetstrcontrol, + stringToCharPtr); + setParamIfPossible_MACRO(mapInteger64Controls_, XPRSsetintcontrol64, + std::stoll); + LOG(ERROR) << "Unknown parameter " << paramName << " : function " + << __FUNCTION__ << std::endl; + return false; + } + return true; +} + +/*****************************************************************************\ +* Name: optimizermsg +* Purpose: Display Optimizer error messages and warnings. +* Arguments: const char *sMsg Message string +* int nLen Message length +* int nMsgLvl Message type +* Return Value: None +\*****************************************************************************/ +void XPRS_CC optimizermsg(XPRSprob prob, void* data, const char* sMsg, int nLen, + int nMsgLvl) { + auto* xprs = reinterpret_cast(data); + if (!xprs->quiet()) { + switch (nMsgLvl) { + /* Print Optimizer error messages and warnings */ + case 4: /* error */ + case 3: /* warning */ + /* Ignore other messages */ + case 2: /* dialogue */ + case 1: /* information */ + printf("%*s\n", nLen, sMsg); + break; + /* Exit and flush buffers */ + default: + fflush(nullptr); + break; + } + } +} + +void XpressInterface::AddSolutionHintToOptimizer() { + // Currently the XPRESS API does not handle clearing out previous hints + const std::size_t len = solver_->solution_hint_.size(); + if (len == 0) { + // hint is empty, nothing to do + return; + } + unique_ptr col_ind(new int[len]); + unique_ptr val(new double[len]); + + for (std::size_t i = 0; i < len; ++i) { + col_ind[i] = solver_->solution_hint_[i].first->index(); + val[i] = solver_->solution_hint_[i].second; + } + addhint(mLp, len, val.get(), col_ind.get()); +} + +void XpressInterface::SetCallback(MPCallback* mp_callback) { + if (callback_ != nullptr) { + // replace existing callback by removing it first + CHECK_STATUS(XPRSremovecbintsol(mLp, XpressIntSolCallbackImpl, NULL)); + } + callback_ = mp_callback; +} + +// This is the call-back called by XPRESS when it finds a new MIP solution +// NOTE(user): This function must have this exact API, because we are passing +// it to XPRESS as a callback. +void XPRS_CC XpressIntSolCallbackImpl(XPRSprob cbprob, void* cbdata) { + auto callback_with_context = static_cast(cbdata); + if (callback_with_context == nullptr || + callback_with_context->GetCallback() == nullptr) { + // nothing to do + return; + } + try { + std::unique_ptr cb_context = + std::make_unique( + &cbprob, MPCallbackEvent::kMipSolution, getnodecnt(cbprob)); + callback_with_context->GetCallback()->RunCallback(cb_context.get()); + } catch (std::exception&) { + callback_with_context->CatchException(cbprob); + } +} + +bool XpressMPCallbackContext::CanQueryVariableValues() { + return Event() == MPCallbackEvent::kMipSolution; +} + +double XpressMPCallbackContext::VariableValue(const MPVariable* variable) { + if (variable_values_.empty()) { + int num_vars = getnumcols(*xprsprob_); + variable_values_.resize(num_vars); + CHECK_STATUS(XPRSgetmipsol(*xprsprob_, variable_values_.data(), 0)); + } + return variable_values_[variable->index()]; +} + +double XpressMPCallbackContext::SuggestSolution( + const absl::flat_hash_map& solution) { + // Currently the XPRESS API does not handle clearing out previous hints + const std::size_t len = solution.size(); + if (len == 0) { + // hint is empty, do nothing + return NAN; + } + if (Event() == MPCallbackEvent::kMipSolution) { + // Currently, XPRESS does not handle adding a new MIP solution inside the + // "cbintsol" callback (cb for new MIP solutions that is used here) + // So we have to prevent the user from adding a solution + // TODO: remove this workaround when it is handled in XPRESS + LOG(WARNING) + << "XPRESS does not currently allow suggesting MIP solutions after " + "a kMipSolution event. Try another call-back."; + return NAN; + } + unique_ptr colind(new int[len]); + unique_ptr val(new double[len]); + int i = 0; + for (const auto& [var, value] : solution) { + colind[i] = var->index(); + val[i] = value; + ++i; + } + addhint(*xprsprob_, len, val.get(), colind.get()); + + // XPRESS doesn't guarantee if nor when it will test the suggested solution. + // So we return NaN because we can't know the actual objective value. + return NAN; +} } // namespace operations_research -#endif // #if defined(USE_XPRESS) diff --git a/ortools/linear_solver/xpress_interface_test.cc b/ortools/linear_solver/xpress_interface_test.cc new file mode 100644 index 0000000000..2c251824f6 --- /dev/null +++ b/ortools/linear_solver/xpress_interface_test.cc @@ -0,0 +1,1362 @@ +#include +#include +#include + +#include "gtest/gtest.h" +#include "ortools/base/init_google.h" +#include "ortools/linear_solver/linear_solver.h" +#include "ortools/xpress/environment.h" +#define XPRS_NAMELENGTH 1028 + +namespace operations_research { + +#define EXPECT_STATUS(s) \ + do { \ + int const status_ = s; \ + EXPECT_EQ(0, status_) << "Nonzero return status"; \ + } while (0) + +class XPRSGetter { + public: + XPRSGetter(MPSolver* solver) : solver_(solver) {} + + int getNumVariables() { + int cols; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_COLS, &cols)); + return cols; + } + + int getNumConstraints() { + int cols; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_ROWS, &cols)); + return cols; + } + + std::string getRowName(int n) { + EXPECT_LT(n, getNumConstraints()); + return getName(n, XPRS_NAMES_ROW); + } + + double getLb(int n) { + EXPECT_LT(n, getNumVariables()); + double lb; + EXPECT_STATUS(XPRSgetlb(prob(), &lb, n, n)); + return lb; + } + + double getUb(int n) { + EXPECT_LT(n, getNumVariables()); + double ub; + EXPECT_STATUS(XPRSgetub(prob(), &ub, n, n)); + return ub; + } + + std::string getColName(int n) { + EXPECT_LT(n, getNumVariables()); + return getName(n, XPRS_NAMES_COLUMN); + } + + char getVariableType(int n) { + EXPECT_LT(n, getNumVariables()); + char type; + EXPECT_STATUS(XPRSgetcoltype(prob(), &type, n, n)); + return type; + } + + char getConstraintType(int n) { + EXPECT_LT(n, getNumConstraints()); + char type; + EXPECT_STATUS(XPRSgetrowtype(prob(), &type, n, n)); + return type; + } + + double getConstraintRhs(int n) { + EXPECT_LT(n, getNumConstraints()); + double rhs; + EXPECT_STATUS(XPRSgetrhs(prob(), &rhs, n, n)); + return rhs; + } + + double getConstraintRange(int n) { + EXPECT_LT(n, getNumConstraints()); + double range; + EXPECT_STATUS(XPRSgetrhsrange(prob(), &range, n, n)); + return range; + } + + double getConstraintCoef(int row, int col) { + EXPECT_LT(col, getNumVariables()); + EXPECT_LT(row, getNumConstraints()); + double coef; + EXPECT_STATUS(XPRSgetcoef(prob(), row, col, &coef)); + return coef; + } + + double getObjectiveCoef(int n) { + EXPECT_LT(n, getNumVariables()); + double objCoef; + EXPECT_STATUS(XPRSgetobj(prob(), &objCoef, n, n)); + return objCoef; + } + + double getObjectiveOffset() { + double offset; + EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJRHS, &offset)); + return offset; + } + + double getObjectiveSense() { + double sense; + EXPECT_STATUS(XPRSgetdblattrib(prob(), XPRS_OBJSENSE, &sense)); + return sense; + } + + std::string getStringControl(int control) { + std::string value(280, '\0'); + int valueSize; + EXPECT_STATUS(XPRSgetstringcontrol(prob(), control, &value[0], value.size(), + &valueSize)); + value.resize(valueSize - 1); + return value; + } + + double getDoubleControl(int control) { + double value; + EXPECT_STATUS(XPRSgetdblcontrol(prob(), control, &value)); + return value; + } + + int getIntegerControl(int control) { + int value; + EXPECT_STATUS(XPRSgetintcontrol(prob(), control, &value)); + return value; + } + + int getInteger64Control(int control) { + XPRSint64 value; + EXPECT_STATUS(XPRSgetintcontrol64(prob(), control, &value)); + return value; + } + + private: + MPSolver* solver_; + + XPRSprob prob() { return (XPRSprob)solver_->underlying_solver(); } + + std::string getName(int n, int type) { + int namelength; + EXPECT_STATUS(XPRSgetintattrib(prob(), XPRS_NAMELENGTH, &namelength)); + + std::string name; + name.resize(8 * namelength + 1); + EXPECT_STATUS(XPRSgetnames(prob(), type, name.data(), n, n)); + + name.erase(std::find_if(name.rbegin(), name.rend(), + [](unsigned char ch) { + return !std::isspace(ch) && ch != '\0'; + }) + .base(), + name.end()); + + return name; + } +}; + +#define UNITTEST_INIT_MIP() \ + MPSolver solver("XPRESS_MIP", MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING); \ + XPRSGetter getter(&solver) +#define UNITTEST_INIT_LP() \ + MPSolver solver("XPRESS_LP", MPSolver::XPRESS_LINEAR_PROGRAMMING); \ + XPRSGetter getter(&solver) + +void _unittest_verify_var(XPRSGetter* getter, MPVariable* x, char type, + double lb, double ub) { + EXPECT_EQ(getter->getVariableType(x->index()), type); + EXPECT_EQ(getter->getLb(x->index()), lb); + EXPECT_EQ(getter->getUb(x->index()), ub); +} + +void _unittest_verify_constraint(XPRSGetter* getter, MPConstraint* c, char type, + double lb, double ub) { + int idx = c->index(); + EXPECT_EQ(getter->getConstraintType(idx), type); + switch (type) { + case 'L': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + break; + case 'U': + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + case 'E': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + EXPECT_EQ(getter->getConstraintRhs(idx), lb); + break; + case 'R': + EXPECT_EQ(getter->getConstraintRhs(idx), ub); + EXPECT_EQ(getter->getConstraintRange(idx), ub - lb); + break; + } +} + +void buildLargeMip(MPSolver& solver, int numVars, int maxTime) { + // Build a random but big and complicated MIP with numVars integer variables + // And every variable has a coupling constraint with all previous ones + srand(123); + MPObjective* obj = solver.MutableObjective(); + obj->SetMaximization(); + for (int i = 0; i < numVars; ++i) { + MPVariable* x = solver.MakeIntVar(-rand() % 200, rand() % 200, + "x_" + std::to_string(i)); + obj->SetCoefficient(x, rand() % 200 - 100); + if (i == 0) { + continue; + } + int rand1 = -rand() % 2000; + int rand2 = rand() % 2000; + int min = std::min(rand1, rand2); + int max = std::max(rand1, rand2); + MPConstraint* c = solver.MakeRowConstraint(min, max); + c->SetCoefficient(x, rand() % 200 - 100); + for (int j = 0; j < i; ++j) { + c->SetCoefficient(solver.variable(j), rand() % 200 - 100); + } + } + solver.SetSolverSpecificParametersAsString("PRESOLVE 0 MAXTIME " + + std::to_string(maxTime)); + solver.EnableOutput(); +} + +void buildLargeLp(MPSolver& solver, int numVars) { + MPObjective* obj = solver.MutableObjective(); + obj->SetMaximization(); + for (int i = 0; i < numVars; ++i) { + MPVariable* x = solver.MakeNumVar(-(i * i) % 21, + (i * i) % 55, + "x_" + std::to_string(i)); + obj->SetCoefficient(x, (i * i) % 23); + int min = -50; + int max = (i * i) % 664 + 55; + MPConstraint* c = solver.MakeRowConstraint(min, max); + c->SetCoefficient(x, i % 331); + for (int j = 0; j < i; ++j) { + c->SetCoefficient(solver.variable(j), i + j); + } + } + solver.EnableOutput(); +} + +class MyMPCallback : public MPCallback { + private: + MPSolver* mpSolver_; + int nSolutions_ = 0; + std::vector last_variable_values_; + bool should_throw_; + + public: + MyMPCallback(MPSolver* mpSolver, bool should_throw) + : MPCallback(false, false), + mpSolver_(mpSolver), + should_throw_(should_throw){}; + + ~MyMPCallback() override{}; + + void RunCallback(MPCallbackContext* callback_context) override { + if (should_throw_) { + throw std::runtime_error("This is a mocked exception in MyMPCallback"); + } + // XpressMPCallbackContext* context_ = + // static_cast(callback_context); + ++nSolutions_; + EXPECT_TRUE(callback_context->CanQueryVariableValues()); + EXPECT_EQ(callback_context->Event(), MPCallbackEvent::kMipSolution); + last_variable_values_.resize(mpSolver_->NumVariables(), 0.0); + for (int i = 0; i < mpSolver_->NumVariables(); i++) { + last_variable_values_[i] = + callback_context->VariableValue(mpSolver_->variable(i)); + } + }; + + int getNSolutions() const { return nSolutions_; } + double getLastVariableValue(int index) const { + return last_variable_values_[index]; + } +}; + +MyMPCallback* buildLargeMipWithCallback(MPSolver& solver, int numVars, + int maxTime) { + buildLargeMip(solver, numVars, maxTime); + MPCallback* mpCallback = new MyMPCallback(&solver, false); + solver.SetCallback(nullptr); // just to test that this does not cause failure + solver.SetCallback(mpCallback); + return static_cast(mpCallback); +} + +TEST(XpressInterface, isMIP) { + UNITTEST_INIT_MIP(); + EXPECT_EQ(solver.IsMIP(), true); +} + +TEST(XpressInterface, isLP) { + UNITTEST_INIT_LP(); + EXPECT_EQ(solver.IsMIP(), false); +} + +TEST(XpressInterface, LpStartingBasis) { + UNITTEST_INIT_LP(); + buildLargeLp(solver, 1000); + // First, we record the number of iterations without an initial basis + solver.Solve(); + const auto iterInit = solver.iterations(); + EXPECT_GE(iterInit, 1000); + + // Here, we retrieve the final basis + std::vector varStatus, constrStatus; + for (auto* var : solver.variables()) { + varStatus.push_back(var->basis_status()); + } + for (auto* constr : solver.constraints()) { + constrStatus.push_back(constr->basis_status()); + } + + // Then we slightly modify the problem... + MPObjective* obj = solver.MutableObjective(); + obj->SetCoefficient(solver.variable(1), 100); + // Here, we provide the final basis of the previous (similar) problem + solver.SetStartingLpBasis(varStatus, constrStatus); + solver.Solve(); + const auto iterWithBasis = solver.iterations(); + // ...and check that few iterations have been performed + EXPECT_LT(iterWithBasis, 10); +} + +TEST(XpressInterface, NumVariables) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeNumVar(-1., 5.1, "x1"); + MPVariable* x2 = solver.MakeNumVar(3.14, 5.1, "x2"); + std::vector xs; + solver.MakeBoolVarArray(500, "xs", &xs); + solver.Solve(); + EXPECT_EQ(getter.getNumVariables(), 502); +} + +TEST(XpressInterface, NumConstraints) { + UNITTEST_INIT_MIP(); + solver.MakeRowConstraint(12., 100.0); + solver.MakeRowConstraint(13., 13.1); + solver.MakeRowConstraint(12.1, 1000.0); + solver.Solve(); + EXPECT_EQ(getter.getNumConstraints(), 3); +} + +TEST(XpressInterface, Reset) { + UNITTEST_INIT_MIP(); + solver.MakeBoolVar("x1"); + solver.MakeBoolVar("x2"); + solver.MakeRowConstraint(12., 100.0); + solver.Solve(); + EXPECT_EQ(getter.getNumConstraints(), 1); + EXPECT_EQ(getter.getNumVariables(), 2); + solver.Reset(); + EXPECT_EQ(getter.getNumConstraints(), 0); + EXPECT_EQ(getter.getNumVariables(), 0); +} + +TEST(XpressInterface, MakeIntVar) { + UNITTEST_INIT_MIP(); + int lb = 0, ub = 10; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'I', lb, ub); +} + +TEST(XpressInterface, MakeNumVar) { + UNITTEST_INIT_MIP(); + double lb = 1.5, ub = 158.2; + MPVariable* x = solver.MakeNumVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'C', lb, ub); +} + +TEST(XpressInterface, MakeBoolVar) { + UNITTEST_INIT_MIP(); + MPVariable* x = solver.MakeBoolVar("x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'B', 0, 1); +} + +TEST(XpressInterface, MakeIntVarArray) { + UNITTEST_INIT_MIP(); + int n1 = 25, lb1 = -7, ub1 = 18; + std::vector xs1; + solver.MakeIntVarArray(n1, lb1, ub1, "xs1", &xs1); + int n2 = 37, lb2 = 19, ub2 = 189; + std::vector xs2; + solver.MakeIntVarArray(n2, lb2, ub2, "xs2", &xs2); + solver.Solve(); + for (int i = 0; i < n1; ++i) { + _unittest_verify_var(&getter, xs1[i], 'I', lb1, ub1); + } + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], 'I', lb2, ub2); + } +} + +TEST(XpressInterface, MakeNumVarArray) { + UNITTEST_INIT_MIP(); + int n1 = 1; + double lb1 = 5.1, ub1 = 8.1; + std::vector xs1; + solver.MakeNumVarArray(n1, lb1, ub1, "xs1", &xs1); + int n2 = 13; + double lb2 = -11.5, ub2 = 189.9; + std::vector xs2; + solver.MakeNumVarArray(n2, lb2, ub2, "xs2", &xs2); + solver.Solve(); + for (int i = 0; i < n1; ++i) { + _unittest_verify_var(&getter, xs1[i], 'C', lb1, ub1); + } + for (int i = 0; i < n2; ++i) { + _unittest_verify_var(&getter, xs2[i], 'C', lb2, ub2); + } +} + +TEST(XpressInterface, MakeBoolVarArray) { + UNITTEST_INIT_MIP(); + double n = 43; + std::vector xs; + solver.MakeBoolVarArray(n, "xs", &xs); + solver.Solve(); + for (int i = 0; i < n; ++i) { + _unittest_verify_var(&getter, xs[i], 'B', 0, 1); + } +} + +TEST(XpressInterface, SetVariableBounds) { + UNITTEST_INIT_MIP(); + int lb1 = 3, ub1 = 4; + MPVariable* x1 = solver.MakeIntVar(lb1, ub1, "x1"); + double lb2 = 3.7, ub2 = 4; + MPVariable* x2 = solver.MakeNumVar(lb2, ub2, "x2"); + solver.Solve(); + _unittest_verify_var(&getter, x1, 'I', lb1, ub1); + _unittest_verify_var(&getter, x2, 'C', lb2, ub2); + lb1 = 12, ub1 = 15; + x1->SetBounds(lb1, ub1); + lb2 = -1.1, ub2 = 0; + x2->SetBounds(lb2, ub2); + solver.Solve(); + _unittest_verify_var(&getter, x1, 'I', lb1, ub1); + _unittest_verify_var(&getter, x2, 'C', lb2, ub2); +} + +TEST(XpressInterface, SetVariableInteger) { + UNITTEST_INIT_MIP(); + int lb = -1, ub = 7; + MPVariable* x = solver.MakeIntVar(lb, ub, "x"); + solver.Solve(); + _unittest_verify_var(&getter, x, 'I', lb, ub); + x->SetInteger(false); + solver.Solve(); + _unittest_verify_var(&getter, x, 'C', lb, ub); +} + +TEST(XpressInterface, ConstraintL) { + UNITTEST_INIT_MIP(); + double lb = -solver.infinity(), ub = 10.; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); +} + +TEST(XpressInterface, ConstraintR) { + UNITTEST_INIT_MIP(); + double lb = -2, ub = -1; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); +} + +TEST(XpressInterface, ConstraintG) { + UNITTEST_INIT_MIP(); + double lb = 8.1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); +} + +TEST(XpressInterface, ConstraintE) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); +} + +TEST(XpressInterface, SetConstraintBoundsL) { + UNITTEST_INIT_MIP(); + double lb = 18947.3, ub = lb; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); + lb = -solver.infinity(), ub = 16.6; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); +} + +TEST(XpressInterface, SetConstraintBoundsR) { + UNITTEST_INIT_MIP(); + double lb = -solver.infinity(), ub = 15; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'L', lb, ub); + lb = 0, ub = 0.1; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); +} + +TEST(XpressInterface, SetConstraintBoundsG) { + UNITTEST_INIT_MIP(); + double lb = 1, ub = 2; + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'R', lb, ub); + lb = 5, ub = solver.infinity(); + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); +} + +TEST(XpressInterface, SetConstraintBoundsE) { + UNITTEST_INIT_MIP(); + double lb = -1, ub = solver.infinity(); + MPConstraint* c = solver.MakeRowConstraint(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'G', lb, ub); + lb = 128, ub = lb; + c->SetBounds(lb, ub); + solver.Solve(); + _unittest_verify_constraint(&getter, c, 'E', lb, ub); +} + +TEST(XpressInterface, ConstraintCoef) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeBoolVar("x1"); + MPVariable* x2 = solver.MakeBoolVar("x2"); + MPConstraint* c1 = solver.MakeRowConstraint(4.1, solver.infinity()); + MPConstraint* c2 = solver.MakeRowConstraint(-solver.infinity(), 0.1); + double c11 = -15.6, c12 = 0.4, c21 = -11, c22 = 4.5; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); + c11 = 0.11, c12 = 0.12, c21 = 0.21, c22 = 0.22; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); +} + +TEST(XpressInterface, ClearConstraint) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeBoolVar("x1"); + MPVariable* x2 = solver.MakeBoolVar("x2"); + MPConstraint* c1 = solver.MakeRowConstraint(4.1, solver.infinity()); + MPConstraint* c2 = solver.MakeRowConstraint(-solver.infinity(), 0.1); + double c11 = -1533.6, c12 = 3.4, c21 = -11000, c22 = 0.0001; + c1->SetCoefficient(x1, c11); + c1->SetCoefficient(x2, c12); + c2->SetCoefficient(x1, c21); + c2->SetCoefficient(x2, c22); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), c11); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), c12); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), c21); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), c22); + c1->Clear(); + c2->Clear(); + solver.Solve(); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x1->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c1->index(), x2->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x1->index()), 0); + EXPECT_EQ(getter.getConstraintCoef(c2->index(), x2->index()), 0); +} + +TEST(XpressInterface, ObjectiveCoef) { + UNITTEST_INIT_MIP(); + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double coef = 3112.4; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); + coef = 0.2; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); +} + +TEST(XpressInterface, ObjectiveOffset) { + UNITTEST_INIT_MIP(); + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double offset = 4.3; + obj->SetOffset(offset); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveOffset(), offset); + offset = 3.6; + obj->SetOffset(offset); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveOffset(), offset); +} + +TEST(XpressInterface, ClearObjective) { + UNITTEST_INIT_MIP(); + MPVariable* x = solver.MakeBoolVar("x"); + MPObjective* obj = solver.MutableObjective(); + double coef = -15.6; + obj->SetCoefficient(x, coef); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), coef); + obj->Clear(); + solver.Solve(); + EXPECT_EQ(getter.getObjectiveCoef(x->index()), 0); +} + +TEST(XpressInterface, ObjectiveSense) { + UNITTEST_INIT_MIP(); + MPObjective* const objective = solver.MutableObjective(); + objective->SetMinimization(); + EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MINIMIZE); + objective->SetMaximization(); + EXPECT_EQ(getter.getObjectiveSense(), XPRS_OBJ_MAXIMIZE); +} + +TEST(XpressInterface, interations) { + UNITTEST_INIT_LP(); + int nc = 100, nv = 100; + std::vector cs(nc); + for (int ci = 0; ci < nc; ++ci) { + cs[ci] = solver.MakeRowConstraint(ci, ci + 1); + } + MPObjective* const objective = solver.MutableObjective(); + for (int vi = 0; vi < nv; ++vi) { + MPVariable* v = solver.MakeNumVar(0, nv, "x" + std::to_string(vi)); + for (int ci = 0; ci < nc; ++ci) { + cs[ci]->SetCoefficient(v, vi + ci); + } + objective->SetCoefficient(v, 1); + } + solver.Solve(); + EXPECT_GT(solver.iterations(), 0); +} + +TEST(XpressInterface, nodes) { + UNITTEST_INIT_MIP(); + int nc = 100, nv = 100; + std::vector cs(nc); + for (int ci = 0; ci < nc; ++ci) { + cs[ci] = solver.MakeRowConstraint(ci, ci + 1); + } + MPObjective* const objective = solver.MutableObjective(); + for (int vi = 0; vi < nv; ++vi) { + MPVariable* v = solver.MakeIntVar(0, nv, "x" + std::to_string(vi)); + for (int ci = 0; ci < nc; ++ci) { + cs[ci]->SetCoefficient(v, vi + ci); + } + objective->SetCoefficient(v, 1); + } + solver.Solve(); + EXPECT_GT(solver.nodes(), 0); +} + +TEST(XpressInterface, SolverVersion) { + UNITTEST_INIT_MIP(); + EXPECT_GE(solver.SolverVersion().size(), 30); +} + +TEST(XpressInterface, Write) { + UNITTEST_INIT_MIP(); + MPVariable* x1 = solver.MakeIntVar(-1.2, 9.3, "C1"); + MPVariable* x2 = solver.MakeNumVar(-1, 5.147593849384714, "C2"); + MPConstraint* c1 = solver.MakeRowConstraint(-solver.infinity(), 1, "R1"); + c1->SetCoefficient(x1, 3); + c1->SetCoefficient(x2, 1.5); + MPConstraint* c2 = solver.MakeRowConstraint(3, 5, "R2"); + c2->SetCoefficient(x2, -1.1122334455667788); + MPObjective* obj = solver.MutableObjective(); + obj->SetMaximization(); + obj->SetCoefficient(x1, 1); + obj->SetCoefficient(x2, 2); + + const std::filesystem::path temporary_working_dir = + std::filesystem::temp_directory_path() / "temporary_working_dir"; + std::filesystem::create_directories(temporary_working_dir); + + std::string tmpName = (temporary_working_dir / "dummy.mps").string(); + solver.Write(tmpName); + + std::ifstream tmpFile(tmpName); + std::stringstream tmpBuffer; + tmpBuffer << tmpFile.rdbuf(); + tmpFile.close(); + std::filesystem::remove_all(temporary_working_dir); + + EXPECT_EQ(tmpBuffer.str(), R"(NAME newProb +OBJSENSE MAXIMIZE +ROWS + N __OBJ___ + L R1 + L R2 +COLUMNS + C1 __OBJ___ 1 + C1 R1 3 + C2 __OBJ___ 2 + C2 R1 1.5 + C2 R2 -1.1122334455667788 +RHS + RHS00001 R1 1 + RHS00001 R2 5 +RANGES + RNG00001 R2 2 +BOUNDS + UI BND00001 C1 9 + LO BND00001 C1 -1 + UP BND00001 C2 5.147593849384714 + LO BND00001 C2 -1 +ENDATA +)"); +} + +TEST(XpressInterface, SetPrimalTolerance) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + double tol = 1e-4; + params.SetDoubleParam(MPSolverParameters::PRIMAL_TOLERANCE, tol); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_FEASTOL), tol); +} + +TEST(XpressInterface, SetDualTolerance) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + double tol = 1e-2; + params.SetDoubleParam(MPSolverParameters::DUAL_TOLERANCE, tol); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_OPTIMALITYTOL), tol); +} + +TEST(XpressInterface, SetPresolveMode) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::PRESOLVE, + MPSolverParameters::PRESOLVE_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 0); + params.SetIntegerParam(MPSolverParameters::PRESOLVE, + MPSolverParameters::PRESOLVE_ON); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_PRESOLVE), 1); +} + +TEST(XpressInterface, SetLpAlgorithm) { + UNITTEST_INIT_LP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::DUAL); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 2); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::PRIMAL); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 3); + params.SetIntegerParam(MPSolverParameters::LP_ALGORITHM, + MPSolverParameters::BARRIER); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_DEFAULTALG), 4); +} + +TEST(XpressInterface, SetScaling) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + params.SetIntegerParam(MPSolverParameters::SCALING, + MPSolverParameters::SCALING_OFF); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 0); + params.SetIntegerParam(MPSolverParameters::SCALING, + MPSolverParameters::SCALING_ON); + solver.Solve(params); + EXPECT_EQ(getter.getIntegerControl(XPRS_SCALING), 163); +} + +TEST(XpressInterface, SetRelativeMipGap) { + UNITTEST_INIT_MIP(); + MPSolverParameters params; + double relativeMipGap = 1e-3; + params.SetDoubleParam(MPSolverParameters::RELATIVE_MIP_GAP, relativeMipGap); + solver.Solve(params); + EXPECT_EQ(getter.getDoubleControl(XPRS_MIPRELSTOP), relativeMipGap); +} + +TEST(XpressInterface, setStringControls) { + std::vector> params = { + {"MPSRHSNAME", XPRS_MPSRHSNAME, "default_value"}, + {"MPSOBJNAME", XPRS_MPSOBJNAME, "default_value"}, + {"MPSRANGENAME", XPRS_MPSRANGENAME, "default_value"}, + {"MPSBOUNDNAME", XPRS_MPSBOUNDNAME, "default_value"}, + {"OUTPUTMASK", XPRS_OUTPUTMASK, "default_value"}, + {"TUNERMETHODFILE", XPRS_TUNERMETHODFILE, "default_value"}, + {"TUNEROUTPUTPATH", XPRS_TUNEROUTPUTPATH, "default_value"}, + {"TUNERSESSIONNAME", XPRS_TUNERSESSIONNAME, "default_value"}, + {"COMPUTEEXECSERVICE", XPRS_COMPUTEEXECSERVICE, "default_value"}, + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = paramString + " " + paramValue; + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getStringControl(control)); + } +} + +TEST(XpressInterface, setDoubleControls) { + std::vector> params = { + {"MAXCUTTIME", XPRS_MAXCUTTIME, 1.}, + {"MAXSTALLTIME", XPRS_MAXSTALLTIME, 1.}, + {"TUNERMAXTIME", XPRS_TUNERMAXTIME, 1.}, + {"MATRIXTOL", XPRS_MATRIXTOL, 1.}, + {"PIVOTTOL", XPRS_PIVOTTOL, 1.}, + {"FEASTOL", XPRS_FEASTOL, 1.}, + {"OUTPUTTOL", XPRS_OUTPUTTOL, 1.}, + {"SOSREFTOL", XPRS_SOSREFTOL, 1.}, + {"OPTIMALITYTOL", XPRS_OPTIMALITYTOL, 1.}, + {"ETATOL", XPRS_ETATOL, 1.}, + {"RELPIVOTTOL", XPRS_RELPIVOTTOL, 1.}, + {"MIPTOL", XPRS_MIPTOL, 1.}, + {"MIPTOLTARGET", XPRS_MIPTOLTARGET, 1.}, + {"BARPERTURB", XPRS_BARPERTURB, 1.}, + {"MIPADDCUTOFF", XPRS_MIPADDCUTOFF, 1.}, + {"MIPABSCUTOFF", XPRS_MIPABSCUTOFF, 1.}, + {"MIPRELCUTOFF", XPRS_MIPRELCUTOFF, 1.}, + {"PSEUDOCOST", XPRS_PSEUDOCOST, 1.}, + {"PENALTY", XPRS_PENALTY, 1.}, + {"BIGM", XPRS_BIGM, 1.}, + {"MIPABSSTOP", XPRS_MIPABSSTOP, 1.}, + {"MIPRELSTOP", XPRS_MIPRELSTOP, 1.}, + {"CROSSOVERACCURACYTOL", XPRS_CROSSOVERACCURACYTOL, 1.}, + {"PRIMALPERTURB", XPRS_PRIMALPERTURB, 1.}, + {"DUALPERTURB", XPRS_DUALPERTURB, 1.}, + {"BAROBJSCALE", XPRS_BAROBJSCALE, 1.}, + {"BARRHSSCALE", XPRS_BARRHSSCALE, 1.}, + {"CHOLESKYTOL", XPRS_CHOLESKYTOL, 1.}, + {"BARGAPSTOP", XPRS_BARGAPSTOP, 1.}, + {"BARDUALSTOP", XPRS_BARDUALSTOP, 1.}, + {"BARPRIMALSTOP", XPRS_BARPRIMALSTOP, 1.}, + {"BARSTEPSTOP", XPRS_BARSTEPSTOP, 1.}, + {"ELIMTOL", XPRS_ELIMTOL, 1.}, + {"MARKOWITZTOL", XPRS_MARKOWITZTOL, 1.}, + {"MIPABSGAPNOTIFY", XPRS_MIPABSGAPNOTIFY, 1.}, + {"MIPRELGAPNOTIFY", XPRS_MIPRELGAPNOTIFY, 1.}, + {"BARLARGEBOUND", XPRS_BARLARGEBOUND, 1.}, + {"PPFACTOR", XPRS_PPFACTOR, 1.}, + {"REPAIRINDEFINITEQMAX", XPRS_REPAIRINDEFINITEQMAX, 1.}, + {"BARGAPTARGET", XPRS_BARGAPTARGET, 1.}, + {"DUMMYCONTROL", XPRS_DUMMYCONTROL, 1.}, + {"BARSTARTWEIGHT", XPRS_BARSTARTWEIGHT, 1.}, + {"BARFREESCALE", XPRS_BARFREESCALE, 1.}, + {"SBEFFORT", XPRS_SBEFFORT, 1.}, + {"HEURDIVERANDOMIZE", XPRS_HEURDIVERANDOMIZE, 1.}, + {"HEURSEARCHEFFORT", XPRS_HEURSEARCHEFFORT, 1.}, + {"CUTFACTOR", XPRS_CUTFACTOR, 1.}, + {"EIGENVALUETOL", XPRS_EIGENVALUETOL, 1.}, + {"INDLINBIGM", XPRS_INDLINBIGM, 1.}, + {"TREEMEMORYSAVINGTARGET", XPRS_TREEMEMORYSAVINGTARGET, 1.}, + {"INDPRELINBIGM", XPRS_INDPRELINBIGM, 1.}, + {"RELAXTREEMEMORYLIMIT", XPRS_RELAXTREEMEMORYLIMIT, 1.}, + {"MIPABSGAPNOTIFYOBJ", XPRS_MIPABSGAPNOTIFYOBJ, 1.}, + {"MIPABSGAPNOTIFYBOUND", XPRS_MIPABSGAPNOTIFYBOUND, 1.}, + {"PRESOLVEMAXGROW", XPRS_PRESOLVEMAXGROW, 1.}, + {"HEURSEARCHTARGETSIZE", XPRS_HEURSEARCHTARGETSIZE, 1.}, + {"CROSSOVERRELPIVOTTOL", XPRS_CROSSOVERRELPIVOTTOL, 1.}, + {"CROSSOVERRELPIVOTTOLSAFE", XPRS_CROSSOVERRELPIVOTTOLSAFE, 1.}, + {"DETLOGFREQ", XPRS_DETLOGFREQ, 1.}, + {"MAXIMPLIEDBOUND", XPRS_MAXIMPLIEDBOUND, 1.}, + {"FEASTOLTARGET", XPRS_FEASTOLTARGET, 1.}, + {"OPTIMALITYTOLTARGET", XPRS_OPTIMALITYTOLTARGET, 1.}, + {"PRECOMPONENTSEFFORT", XPRS_PRECOMPONENTSEFFORT, 1.}, + {"LPLOGDELAY", XPRS_LPLOGDELAY, 1.}, + {"HEURDIVEITERLIMIT", XPRS_HEURDIVEITERLIMIT, 1.}, + {"BARKERNEL", XPRS_BARKERNEL, 1.}, + {"FEASTOLPERTURB", XPRS_FEASTOLPERTURB, 1.}, + {"CROSSOVERFEASWEIGHT", XPRS_CROSSOVERFEASWEIGHT, 1.}, + {"LUPIVOTTOL", XPRS_LUPIVOTTOL, 1.}, + {"MIPRESTARTGAPTHRESHOLD", XPRS_MIPRESTARTGAPTHRESHOLD, 1.}, + {"NODEPROBINGEFFORT", XPRS_NODEPROBINGEFFORT, 1.}, + {"INPUTTOL", XPRS_INPUTTOL, 1.}, + {"MIPRESTARTFACTOR", XPRS_MIPRESTARTFACTOR, 1.}, + {"BAROBJPERTURB", XPRS_BAROBJPERTURB, 1.}, + {"CPIALPHA", XPRS_CPIALPHA, 1.}, + {"GLOBALBOUNDINGBOX", XPRS_GLOBALBOUNDINGBOX, 1.}, + {"TIMELIMIT", XPRS_TIMELIMIT, 1.}, + {"SOLTIMELIMIT", XPRS_SOLTIMELIMIT, 1.}, + {"REPAIRINFEASTIMELIMIT", XPRS_REPAIRINFEASTIMELIMIT, 1.}, + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getDoubleControl(control)); + } +} + +TEST(XpressInterface, setIntControl) { + std::vector> params = { + {"EXTRAROWS", XPRS_EXTRAROWS, 1}, + {"EXTRACOLS", XPRS_EXTRACOLS, 1}, + {"LPITERLIMIT", XPRS_LPITERLIMIT, 1}, + {"LPLOG", XPRS_LPLOG, 1}, + {"SCALING", XPRS_SCALING, 1}, + {"PRESOLVE", XPRS_PRESOLVE, 1}, + {"CRASH", XPRS_CRASH, 1}, + {"PRICINGALG", XPRS_PRICINGALG, 1}, + {"INVERTFREQ", XPRS_INVERTFREQ, 1}, + {"INVERTMIN", XPRS_INVERTMIN, 1}, + {"MAXNODE", XPRS_MAXNODE, 1}, + {"MAXTIME", XPRS_MAXTIME, 1}, + {"MAXMIPSOL", XPRS_MAXMIPSOL, 1}, + {"SIFTPASSES", XPRS_SIFTPASSES, 1}, + {"DEFAULTALG", XPRS_DEFAULTALG, 1}, + {"VARSELECTION", XPRS_VARSELECTION, 1}, + {"NODESELECTION", XPRS_NODESELECTION, 1}, + {"BACKTRACK", XPRS_BACKTRACK, 1}, + {"MIPLOG", XPRS_MIPLOG, 1}, + {"KEEPNROWS", XPRS_KEEPNROWS, 1}, + {"MPSECHO", XPRS_MPSECHO, 1}, + {"MAXPAGELINES", XPRS_MAXPAGELINES, 1}, + {"OUTPUTLOG", XPRS_OUTPUTLOG, 1}, + {"BARSOLUTION", XPRS_BARSOLUTION, 1}, + {"CACHESIZE", XPRS_CACHESIZE, 1}, + {"CROSSOVER", XPRS_CROSSOVER, 1}, + {"BARITERLIMIT", XPRS_BARITERLIMIT, 1}, + {"CHOLESKYALG", XPRS_CHOLESKYALG, 1}, + {"BAROUTPUT", XPRS_BAROUTPUT, 1}, + {"EXTRAMIPENTS", XPRS_EXTRAMIPENTS, 1}, + {"REFACTOR", XPRS_REFACTOR, 1}, + {"BARTHREADS", XPRS_BARTHREADS, 1}, + {"KEEPBASIS", XPRS_KEEPBASIS, 1}, + {"CROSSOVEROPS", XPRS_CROSSOVEROPS, 1}, + {"VERSION", XPRS_VERSION, 1}, + {"CROSSOVERTHREADS", XPRS_CROSSOVERTHREADS, 1}, + {"BIGMMETHOD", XPRS_BIGMMETHOD, 1}, + {"MPSNAMELENGTH", XPRS_MPSNAMELENGTH, 1}, + {"ELIMFILLIN", XPRS_ELIMFILLIN, 1}, + {"PRESOLVEOPS", XPRS_PRESOLVEOPS, 1}, + {"MIPPRESOLVE", XPRS_MIPPRESOLVE, 1}, + {"MIPTHREADS", XPRS_MIPTHREADS, 1}, + {"BARORDER", XPRS_BARORDER, 1}, + {"BREADTHFIRST", XPRS_BREADTHFIRST, 1}, + {"AUTOPERTURB", XPRS_AUTOPERTURB, 1}, + {"DENSECOLLIMIT", XPRS_DENSECOLLIMIT, 1}, + {"CALLBACKFROMMASTERTHREAD", XPRS_CALLBACKFROMMASTERTHREAD, 1}, + {"MAXMCOEFFBUFFERELEMS", XPRS_MAXMCOEFFBUFFERELEMS, 1}, + {"REFINEOPS", XPRS_REFINEOPS, 1}, + {"LPREFINEITERLIMIT", XPRS_LPREFINEITERLIMIT, 1}, + {"MIPREFINEITERLIMIT", XPRS_MIPREFINEITERLIMIT, 1}, + {"DUALIZEOPS", XPRS_DUALIZEOPS, 1}, + {"CROSSOVERITERLIMIT", XPRS_CROSSOVERITERLIMIT, 1}, + {"PREBASISRED", XPRS_PREBASISRED, 1}, + {"PRESORT", XPRS_PRESORT, 1}, + {"PREPERMUTE", XPRS_PREPERMUTE, 1}, + {"PREPERMUTESEED", XPRS_PREPERMUTESEED, 1}, + {"MAXMEMORYSOFT", XPRS_MAXMEMORYSOFT, 1}, + {"CUTFREQ", XPRS_CUTFREQ, 1}, + {"SYMSELECT", XPRS_SYMSELECT, 1}, + {"SYMMETRY", XPRS_SYMMETRY, 1}, + {"MAXMEMORYHARD", XPRS_MAXMEMORYHARD, 1}, + {"MIQCPALG", XPRS_MIQCPALG, 1}, + {"QCCUTS", XPRS_QCCUTS, 1}, + {"QCROOTALG", XPRS_QCROOTALG, 1}, + {"PRECONVERTSEPARABLE", XPRS_PRECONVERTSEPARABLE, 1}, + {"ALGAFTERNETWORK", XPRS_ALGAFTERNETWORK, 1}, + {"TRACE", XPRS_TRACE, 1}, + {"MAXIIS", XPRS_MAXIIS, 1}, + {"CPUTIME", XPRS_CPUTIME, 1}, + {"COVERCUTS", XPRS_COVERCUTS, 1}, + {"GOMCUTS", XPRS_GOMCUTS, 1}, + {"LPFOLDING", XPRS_LPFOLDING, 1}, + {"MPSFORMAT", XPRS_MPSFORMAT, 1}, + {"CUTSTRATEGY", XPRS_CUTSTRATEGY, 1}, + {"CUTDEPTH", XPRS_CUTDEPTH, 1}, + {"TREECOVERCUTS", XPRS_TREECOVERCUTS, 1}, + {"TREEGOMCUTS", XPRS_TREEGOMCUTS, 1}, + {"CUTSELECT", XPRS_CUTSELECT, 1}, + {"TREECUTSELECT", XPRS_TREECUTSELECT, 1}, + {"DUALIZE", XPRS_DUALIZE, 1}, + {"DUALGRADIENT", XPRS_DUALGRADIENT, 1}, + {"SBITERLIMIT", XPRS_SBITERLIMIT, 1}, + {"SBBEST", XPRS_SBBEST, 1}, + {"BARINDEFLIMIT", XPRS_BARINDEFLIMIT, 1}, + {"HEURFREQ", XPRS_HEURFREQ, 1}, + {"HEURDEPTH", XPRS_HEURDEPTH, 1}, + {"HEURMAXSOL", XPRS_HEURMAXSOL, 1}, + {"HEURNODES", XPRS_HEURNODES, 1}, + {"LNPBEST", XPRS_LNPBEST, 1}, + {"LNPITERLIMIT", XPRS_LNPITERLIMIT, 1}, + {"BRANCHCHOICE", XPRS_BRANCHCHOICE, 1}, + {"BARREGULARIZE", XPRS_BARREGULARIZE, 1}, + {"SBSELECT", XPRS_SBSELECT, 1}, + {"LOCALCHOICE", XPRS_LOCALCHOICE, 1}, + {"LOCALBACKTRACK", XPRS_LOCALBACKTRACK, 1}, + {"DUALSTRATEGY", XPRS_DUALSTRATEGY, 1}, + {"L1CACHE", XPRS_L1CACHE, 1}, + {"HEURDIVESTRATEGY", XPRS_HEURDIVESTRATEGY, 1}, + {"HEURSELECT", XPRS_HEURSELECT, 1}, + {"BARSTART", XPRS_BARSTART, 1}, + {"PRESOLVEPASSES", XPRS_PRESOLVEPASSES, 1}, + {"BARNUMSTABILITY", XPRS_BARNUMSTABILITY, 1}, + {"BARORDERTHREADS", XPRS_BARORDERTHREADS, 1}, + {"EXTRASETS", XPRS_EXTRASETS, 1}, + {"FEASIBILITYPUMP", XPRS_FEASIBILITYPUMP, 1}, + {"PRECOEFELIM", XPRS_PRECOEFELIM, 1}, + {"PREDOMCOL", XPRS_PREDOMCOL, 1}, + {"HEURSEARCHFREQ", XPRS_HEURSEARCHFREQ, 1}, + {"HEURDIVESPEEDUP", XPRS_HEURDIVESPEEDUP, 1}, + {"SBESTIMATE", XPRS_SBESTIMATE, 1}, + {"BARCORES", XPRS_BARCORES, 1}, + {"MAXCHECKSONMAXTIME", XPRS_MAXCHECKSONMAXTIME, 1}, + {"MAXCHECKSONMAXCUTTIME", XPRS_MAXCHECKSONMAXCUTTIME, 1}, + {"HISTORYCOSTS", XPRS_HISTORYCOSTS, 1}, + {"ALGAFTERCROSSOVER", XPRS_ALGAFTERCROSSOVER, 1}, + {"MUTEXCALLBACKS", XPRS_MUTEXCALLBACKS, 1}, + {"BARCRASH", XPRS_BARCRASH, 1}, + {"HEURDIVESOFTROUNDING", XPRS_HEURDIVESOFTROUNDING, 1}, + {"HEURSEARCHROOTSELECT", XPRS_HEURSEARCHROOTSELECT, 1}, + {"HEURSEARCHTREESELECT", XPRS_HEURSEARCHTREESELECT, 1}, + {"MPS18COMPATIBLE", XPRS_MPS18COMPATIBLE, 1}, + {"ROOTPRESOLVE", XPRS_ROOTPRESOLVE, 1}, + {"CROSSOVERDRP", XPRS_CROSSOVERDRP, 1}, + {"FORCEOUTPUT", XPRS_FORCEOUTPUT, 1}, + {"PRIMALOPS", XPRS_PRIMALOPS, 1}, + {"DETERMINISTIC", XPRS_DETERMINISTIC, 1}, + {"PREPROBING", XPRS_PREPROBING, 1}, + {"TREEMEMORYLIMIT", XPRS_TREEMEMORYLIMIT, 1}, + {"TREECOMPRESSION", XPRS_TREECOMPRESSION, 1}, + {"TREEDIAGNOSTICS", XPRS_TREEDIAGNOSTICS, 1}, + {"MAXTREEFILESIZE", XPRS_MAXTREEFILESIZE, 1}, + {"PRECLIQUESTRATEGY", XPRS_PRECLIQUESTRATEGY, 1}, + {"REPAIRINFEASMAXTIME", XPRS_REPAIRINFEASMAXTIME, 1}, + {"IFCHECKCONVEXITY", XPRS_IFCHECKCONVEXITY, 1}, + {"PRIMALUNSHIFT", XPRS_PRIMALUNSHIFT, 1}, + {"REPAIRINDEFINITEQ", XPRS_REPAIRINDEFINITEQ, 1}, + {"MIPRAMPUP", XPRS_MIPRAMPUP, 1}, + {"MAXLOCALBACKTRACK", XPRS_MAXLOCALBACKTRACK, 1}, + {"USERSOLHEURISTIC", XPRS_USERSOLHEURISTIC, 1}, + {"FORCEPARALLELDUAL", XPRS_FORCEPARALLELDUAL, 1}, + {"BACKTRACKTIE", XPRS_BACKTRACKTIE, 1}, + {"BRANCHDISJ", XPRS_BRANCHDISJ, 1}, + {"MIPFRACREDUCE", XPRS_MIPFRACREDUCE, 1}, + {"CONCURRENTTHREADS", XPRS_CONCURRENTTHREADS, 1}, + {"MAXSCALEFACTOR", XPRS_MAXSCALEFACTOR, 1}, + {"HEURTHREADS", XPRS_HEURTHREADS, 1}, + {"THREADS", XPRS_THREADS, 1}, + {"HEURBEFORELP", XPRS_HEURBEFORELP, 1}, + {"PREDOMROW", XPRS_PREDOMROW, 1}, + {"BRANCHSTRUCTURAL", XPRS_BRANCHSTRUCTURAL, 1}, + {"QUADRATICUNSHIFT", XPRS_QUADRATICUNSHIFT, 1}, + {"BARPRESOLVEOPS", XPRS_BARPRESOLVEOPS, 1}, + {"QSIMPLEXOPS", XPRS_QSIMPLEXOPS, 1}, + {"MIPRESTART", XPRS_MIPRESTART, 1}, + {"CONFLICTCUTS", XPRS_CONFLICTCUTS, 1}, + {"PREPROTECTDUAL", XPRS_PREPROTECTDUAL, 1}, + {"CORESPERCPU", XPRS_CORESPERCPU, 1}, + {"RESOURCESTRATEGY", XPRS_RESOURCESTRATEGY, 1}, + {"CLAMPING", XPRS_CLAMPING, 1}, + {"SLEEPONTHREADWAIT", XPRS_SLEEPONTHREADWAIT, 1}, + {"PREDUPROW", XPRS_PREDUPROW, 1}, + {"CPUPLATFORM", XPRS_CPUPLATFORM, 1}, + {"BARALG", XPRS_BARALG, 1}, + {"SIFTING", XPRS_SIFTING, 1}, + {"LPLOGSTYLE", XPRS_LPLOGSTYLE, 1}, + {"RANDOMSEED", XPRS_RANDOMSEED, 1}, + {"TREEQCCUTS", XPRS_TREEQCCUTS, 1}, + {"PRELINDEP", XPRS_PRELINDEP, 1}, + {"DUALTHREADS", XPRS_DUALTHREADS, 1}, + {"PREOBJCUTDETECT", XPRS_PREOBJCUTDETECT, 1}, + {"PREBNDREDQUAD", XPRS_PREBNDREDQUAD, 1}, + {"PREBNDREDCONE", XPRS_PREBNDREDCONE, 1}, + {"PRECOMPONENTS", XPRS_PRECOMPONENTS, 1}, + {"MAXMIPTASKS", XPRS_MAXMIPTASKS, 1}, + {"MIPTERMINATIONMETHOD", XPRS_MIPTERMINATIONMETHOD, 1}, + {"PRECONEDECOMP", XPRS_PRECONEDECOMP, 1}, + {"HEURFORCESPECIALOBJ", XPRS_HEURFORCESPECIALOBJ, 1}, + {"HEURSEARCHROOTCUTFREQ", XPRS_HEURSEARCHROOTCUTFREQ, 1}, + {"PREELIMQUAD", XPRS_PREELIMQUAD, 1}, + {"PREIMPLICATIONS", XPRS_PREIMPLICATIONS, 1}, + {"TUNERMODE", XPRS_TUNERMODE, 1}, + {"TUNERMETHOD", XPRS_TUNERMETHOD, 1}, + {"TUNERTARGET", XPRS_TUNERTARGET, 1}, + {"TUNERTHREADS", XPRS_TUNERTHREADS, 1}, + {"TUNERHISTORY", XPRS_TUNERHISTORY, 1}, + {"TUNERPERMUTE", XPRS_TUNERPERMUTE, 1}, + {"TUNERVERBOSE", XPRS_TUNERVERBOSE, 1}, + {"TUNEROUTPUT", XPRS_TUNEROUTPUT, 1}, + {"PREANALYTICCENTER", XPRS_PREANALYTICCENTER, 1}, + {"NETCUTS", XPRS_NETCUTS, 1}, + {"LPFLAGS", XPRS_LPFLAGS, 1}, + {"MIPKAPPAFREQ", XPRS_MIPKAPPAFREQ, 1}, + {"OBJSCALEFACTOR", XPRS_OBJSCALEFACTOR, 1}, + {"TREEFILELOGINTERVAL", XPRS_TREEFILELOGINTERVAL, 1}, + {"IGNORECONTAINERCPULIMIT", XPRS_IGNORECONTAINERCPULIMIT, 1}, + {"IGNORECONTAINERMEMORYLIMIT", XPRS_IGNORECONTAINERMEMORYLIMIT, 1}, + {"MIPDUALREDUCTIONS", XPRS_MIPDUALREDUCTIONS, 1}, + {"GENCONSDUALREDUCTIONS", XPRS_GENCONSDUALREDUCTIONS, 1}, + {"PWLDUALREDUCTIONS", XPRS_PWLDUALREDUCTIONS, 1}, + {"BARFAILITERLIMIT", XPRS_BARFAILITERLIMIT, 1}, + {"AUTOSCALING", XPRS_AUTOSCALING, 1}, + {"GENCONSABSTRANSFORMATION", XPRS_GENCONSABSTRANSFORMATION, 1}, + {"COMPUTEJOBPRIORITY", XPRS_COMPUTEJOBPRIORITY, 1}, + {"PREFOLDING", XPRS_PREFOLDING, 1}, + {"NETSTALLLIMIT", XPRS_NETSTALLLIMIT, 1}, + {"SERIALIZEPREINTSOL", XPRS_SERIALIZEPREINTSOL, 1}, + {"NUMERICALEMPHASIS", XPRS_NUMERICALEMPHASIS, 1}, + {"PWLNONCONVEXTRANSFORMATION", XPRS_PWLNONCONVEXTRANSFORMATION, 1}, + {"MIPCOMPONENTS", XPRS_MIPCOMPONENTS, 1}, + {"MIPCONCURRENTNODES", XPRS_MIPCONCURRENTNODES, 1}, + {"MIPCONCURRENTSOLVES", XPRS_MIPCONCURRENTSOLVES, 1}, + {"OUTPUTCONTROLS", XPRS_OUTPUTCONTROLS, 1}, + {"SIFTSWITCH", XPRS_SIFTSWITCH, 1}, + {"HEUREMPHASIS", XPRS_HEUREMPHASIS, 1}, + {"COMPUTEMATX", XPRS_COMPUTEMATX, 1}, + {"COMPUTEMATX_IIS", XPRS_COMPUTEMATX_IIS, 1}, + {"COMPUTEMATX_IISMAXTIME", XPRS_COMPUTEMATX_IISMAXTIME, 1}, + {"BARREFITER", XPRS_BARREFITER, 1}, + {"COMPUTELOG", XPRS_COMPUTELOG, 1}, + {"SIFTPRESOLVEOPS", XPRS_SIFTPRESOLVEOPS, 1}, + {"CHECKINPUTDATA", XPRS_CHECKINPUTDATA, 1}, + {"ESCAPENAMES", XPRS_ESCAPENAMES, 1}, + {"IOTIMEOUT", XPRS_IOTIMEOUT, 1}, + {"AUTOCUTTING", XPRS_AUTOCUTTING, 1}, + {"CALLBACKCHECKTIMEDELAY", XPRS_CALLBACKCHECKTIMEDELAY, 1}, + {"MULTIOBJOPS", XPRS_MULTIOBJOPS, 1}, + {"MULTIOBJLOG", XPRS_MULTIOBJLOG, 1}, + {"GLOBALSPATIALBRANCHIFPREFERORIG", XPRS_GLOBALSPATIALBRANCHIFPREFERORIG, + 1}, + {"PRECONFIGURATION", XPRS_PRECONFIGURATION, 1}, + {"FEASIBILITYJUMP", XPRS_FEASIBILITYJUMP, 1}, + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getIntegerControl(control)); + } +} + +TEST(XpressInterface, setInt64Control) { + std::vector> params = { + {"EXTRAELEMS", XPRS_EXTRAELEMS, 1}, + {"EXTRASETELEMS", XPRS_EXTRASETELEMS, 1}, + }; + for (const auto& [paramString, control, paramValue] : params) { + UNITTEST_INIT_MIP(); + std::string xpressParamString = + paramString + " " + std::to_string(paramValue); + solver.SetSolverSpecificParametersAsString(xpressParamString); + EXPECT_EQ(paramValue, getter.getInteger64Control(control)); + } +} + +TEST(XpressInterface, SolveMIP) { + UNITTEST_INIT_MIP(); + + // max x + 2y + // st. -x + y <= 1 + // 2x + 3y <= 12 + // 3x + 2y <= 12 + // x , y >= 0 + // x , y \in Z + + double inf = solver.infinity(); + MPVariable* x = solver.MakeIntVar(0, inf, "x"); + MPVariable* y = solver.MakeIntVar(0, inf, "y"); + MPObjective* obj = solver.MutableObjective(); + obj->SetCoefficient(x, 1); + obj->SetCoefficient(y, 2); + obj->SetMaximization(); + MPConstraint* c1 = solver.MakeRowConstraint(-inf, 1); + c1->SetCoefficient(x, -1); + c1->SetCoefficient(y, 1); + MPConstraint* c2 = solver.MakeRowConstraint(-inf, 12); + c2->SetCoefficient(x, 3); + c2->SetCoefficient(y, 2); + MPConstraint* c3 = solver.MakeRowConstraint(-inf, 12); + c3->SetCoefficient(x, 2); + c3->SetCoefficient(y, 3); + solver.Solve(); + + EXPECT_EQ(obj->Value(), 6); + EXPECT_EQ(obj->BestBound(), 6); + EXPECT_EQ(x->solution_value(), 2); + EXPECT_EQ(y->solution_value(), 2); +} + +TEST(XpressInterface, SolveLP) { + UNITTEST_INIT_LP(); + + // max x + 2y + // st. -x + y <= 1 + // 2x + 3y <= 12 + // 3x + 2y <= 12 + // x , y \in R+ + + double inf = solver.infinity(); + MPVariable* x = solver.MakeNumVar(0, inf, "x"); + MPVariable* y = solver.MakeNumVar(0, inf, "y"); + MPObjective* obj = solver.MutableObjective(); + obj->SetCoefficient(x, 1); + obj->SetCoefficient(y, 2); + obj->SetMaximization(); + MPConstraint* c1 = solver.MakeRowConstraint(-inf, 1); + c1->SetCoefficient(x, -1); + c1->SetCoefficient(y, 1); + MPConstraint* c2 = solver.MakeRowConstraint(-inf, 12); + c2->SetCoefficient(x, 3); + c2->SetCoefficient(y, 2); + MPConstraint* c3 = solver.MakeRowConstraint(-inf, 12); + c3->SetCoefficient(x, 2); + c3->SetCoefficient(y, 3); + solver.Solve(); + + EXPECT_NEAR(obj->Value(), 7.4, 1e-8); + EXPECT_NEAR(x->solution_value(), 1.8, 1e-8); + EXPECT_NEAR(y->solution_value(), 2.8, 1e-8); + EXPECT_NEAR(x->reduced_cost(), 0, 1e-8); + EXPECT_NEAR(y->reduced_cost(), 0, 1e-8); + EXPECT_NEAR(c1->dual_value(), 0.2, 1e-8); + EXPECT_NEAR(c2->dual_value(), 0, 1e-8); + EXPECT_NEAR(c3->dual_value(), 0.6, 1e-8); +} + +// WARNING fragile test because it uses +// the random generator is used by +// buildLargeMip(solver, numVars, maxTime); +// called by +// buildLargeMipWithCallback(solver, 60, 2); +// This tests hints a solution to the solver that is only +// usable for the test generated under linux +#if defined(_MSC_VER) +// Ignore this test because the random generator is different +// for windows and linux. +#elif defined(__GNUC__) +TEST(XpressInterface, SetHint) { + UNITTEST_INIT_MIP(); + + // Once a solution is added to XPRESS, it is actually impossible to get it + // back using the API + // In this test we send the (near) optimal solution as a hint (with + // obj=56774). Usually XPRESS finds it in ~3000 seconds but in this case it + // should be able to retain it in juste a few seconds using the hint. Note + // that the logs should mention "User solution (USER_HINT) stored." + buildLargeMipWithCallback(solver, 60, 2); + + std::vector hintValues{ + -2, -3, -19, 8, -1, -1, 7, 9, -20, -17, 7, -7, + 9, -27, 13, 14, -6, -3, -25, -9, 15, 13, -10, 16, + -34, 51, 39, 4, -54, 19, -76, 1, -17, -18, -46, -10, + 0, -36, 9, -29, -6, 4, -16, -45, -12, -45, -25, -70, + -43, -63, 54, -148, 79, -2, 64, 92, 61, -121, -174, -85}; + std::vector> hint; + for (int i = 0; i < solver.NumVariables(); ++i) { + hint.push_back(std::make_pair( + solver.LookupVariableOrNull("x_" + std::to_string(i)), hintValues[i])); + } + solver.SetHint(hint); + solver.Solve(); + + // Test that we have at least the near optimal objective function value + EXPECT_GE(solver.Objective().Value(), 56774.0); +} +#endif + + +TEST(XpressInterface, SetCallBack) { + UNITTEST_INIT_MIP(); + + auto myMpCallback = buildLargeMipWithCallback(solver, 30, 30); + solver.Solve(); + + int nSolutions = myMpCallback->getNSolutions(); + + // This is a tough MIP, in 30 seconds XPRESS should have found at least 5 + // solutions (tested with XPRESS v9.0, may change in later versions) + EXPECT_GT(nSolutions, 5); + // Test variable values for the last solution found + for (int i = 0; i < solver.NumVariables(); ++i) { + EXPECT_NEAR( + myMpCallback->getLastVariableValue(i), + solver.LookupVariableOrNull("x_" + std::to_string(i))->solution_value(), + 1e-10); + } +} + +TEST(XpressInterface, SetAndUnsetCallBack) { + // Test that when we unset a callback it is not called + UNITTEST_INIT_MIP(); + auto myMpCallback = buildLargeMipWithCallback(solver, 100, 5); + solver.SetCallback(nullptr); + solver.Solve(); + EXPECT_EQ(myMpCallback->getNSolutions(), 0); +} + +TEST(XpressInterface, SetAndResetCallBack) { + // Test that when we set a new callback then it is called, and old one is not + // called + UNITTEST_INIT_MIP(); + auto oldMpCallback = buildLargeMipWithCallback(solver, 100, 5); + auto newMpCallback = new MyMPCallback(&solver, false); + solver.SetCallback((MPCallback*)newMpCallback); + solver.Solve(); + EXPECT_EQ(oldMpCallback->getNSolutions(), 0); + EXPECT_GT(newMpCallback->getNSolutions(), 1); +} + +TEST(XpressInterface, CallbackThrowsException) { + // Test that when the callback throws an exception, it is caught and logged + UNITTEST_INIT_MIP(); + auto oldMpCallback = buildLargeMipWithCallback(solver, 30, 30); + auto newMpCallback = new MyMPCallback(&solver, true); + solver.SetCallback((MPCallback*)newMpCallback); + testing::internal::CaptureStderr(); + EXPECT_NO_THROW(solver.Solve()); + std::string errors = testing::internal::GetCapturedStderr(); + // Test that StdErr contains the following error message + std::string expected_error = "Caught exception during user-defined call-back: This is a mocked exception in MyMPCallback"; + ASSERT_NE(errors.find(expected_error), std::string::npos); +} + +} // namespace operations_research + +int main(int argc, char** argv) { + absl::SetFlag(&FLAGS_logtostderr, 1); + testing::InitGoogleTest(&argc, argv); + auto solver = operations_research::MPSolver::CreateSolver("XPRESS_LP"); + if (solver == nullptr) { + LOG(ERROR) << "Xpress solver is not available"; + return EXIT_SUCCESS; + } + else{ + return RUN_ALL_TESTS(); + } +} diff --git a/ortools/xpress/BUILD.bazel b/ortools/xpress/BUILD.bazel new file mode 100644 index 0000000000..bbdfb3c815 --- /dev/null +++ b/ortools/xpress/BUILD.bazel @@ -0,0 +1,22 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "environment", + srcs = [ + "environment.cc", + ], + hdrs = [ + "environment.h", + ], + deps = [ + "//ortools/base", + "//ortools/base:dynamic_library", + "//ortools/base:file", + "//ortools/base:status_macros", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:optional", + ], +) diff --git a/ortools/xpress/CMakeLists.txt b/ortools/xpress/CMakeLists.txt new file mode 100644 index 0000000000..bce9253cec --- /dev/null +++ b/ortools/xpress/CMakeLists.txt @@ -0,0 +1,21 @@ +file(GLOB _SRCS "*.h" "*.cc") +set(NAME ${PROJECT_NAME}_xpress) + +add_library(${NAME} OBJECT ${_SRCS}) +set_target_properties(${NAME} PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON + ) +target_include_directories(${NAME} PRIVATE + ${PROJECT_SOURCE_DIR} + ${PROJECT_BINARY_DIR}) +target_link_libraries(${NAME} PRIVATE + absl::hash + absl::meta + absl::memory + absl::strings + absl::str_format + protobuf::libprotobuf + ${PROJECT_NAMESPACE}::${PROJECT_NAME}_proto) diff --git a/ortools/xpress/environment.cc b/ortools/xpress/environment.cc new file mode 100644 index 0000000000..681840b72c --- /dev/null +++ b/ortools/xpress/environment.cc @@ -0,0 +1,362 @@ +// Copyright 2019-2023 RTE +// 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. + +// Initial version of this code was provided by RTE + +#include "ortools/xpress/environment.h" + +#include +#include +#include + +#include "absl/status/status.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" +#include "absl/synchronization/mutex.h" +#include "ortools/base/logging.h" + +namespace operations_research { + +#define STRINGIFY2(X) #X +#define STRINGIFY(X) STRINGIFY2(X) + +// This was generated with the parse_header_xpress.py script. +// See the comment at the top of the script. + +// This is the 'define' section. +std::function XPRScreateprob = nullptr; +std::function XPRSdestroyprob = nullptr; +std::function XPRSinit = nullptr; +std::function XPRSfree = nullptr; +std::function XPRSgetlicerrmsg = nullptr; +std::function XPRSlicense = nullptr; +std::function XPRSgetbanner = nullptr; +std::function XPRSgetversion = nullptr; +std::function XPRSsetdefaultcontrol = nullptr; +std::function XPRSinterrupt = nullptr; +std::function XPRSsetintcontrol = nullptr; +std::function XPRSsetintcontrol64 = nullptr; +std::function XPRSsetdblcontrol = nullptr; +std::function XPRSsetstrcontrol = nullptr; +std::function XPRSgetintcontrol = nullptr; +std::function XPRSgetintcontrol64 = nullptr; +std::function XPRSgetdblcontrol = nullptr; +std::function XPRSgetstringcontrol = nullptr; +std::function XPRSgetintattrib = nullptr; +std::function XPRSgetdblattrib = nullptr; +std::function XPRSgetcontrolinfo = nullptr; +std::function XPRSloadlp = nullptr; +std::function XPRSloadlp64 = nullptr; +std::function XPRSgetobj = nullptr; +std::function XPRSgetrhs = nullptr; +std::function XPRSgetrhsrange = nullptr; +std::function XPRSgetlb = nullptr; +std::function XPRSgetub = nullptr; +std::function XPRSgetcoef = nullptr; +std::function XPRSaddrows = nullptr; +std::function XPRSdelrows = nullptr; +std::function XPRSaddcols = nullptr; +std::function XPRSaddnames = nullptr; +std::function XPRSgetnames = nullptr; +std::function XPRSdelcols = nullptr; +std::function XPRSchgcoltype = nullptr; +std::function XPRSloadbasis = nullptr; +std::function XPRSpostsolve = nullptr; +std::function XPRSchgobjsense = nullptr; +std::function XPRSgetlasterror = nullptr; +std::function XPRSgetbasis = nullptr; +std::function XPRSwriteprob = nullptr; +std::function XPRSgetrowtype = nullptr; +std::function XPRSgetcoltype = nullptr; +std::function XPRSchgbounds = nullptr; +std::function XPRSaddmipsol = nullptr; +std::function XPRSgetlpsol = nullptr; +std::function XPRSgetmipsol = nullptr; +std::function XPRSchgobj = nullptr; +std::function XPRSchgcoef = nullptr; +std::function XPRSchgmcoef = nullptr; +std::function XPRSchgrhs = nullptr; +std::function XPRSchgrhsrange = nullptr; +std::function XPRSchgrowtype = nullptr; +std::function XPRSaddcbintsol = nullptr; +std::function XPRSremovecbintsol = nullptr; +std::function XPRSaddcbmessage = nullptr; +std::function XPRSlpoptimize = nullptr; +std::function XPRSmipoptimize = nullptr; + +void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) { + // This was generated with the parse_header_xpress.py script. + // See the comment at the top of the script. + + // This is the 'assign' section. + xpress_dynamic_library->GetFunction(&XPRScreateprob, "XPRScreateprob"); + xpress_dynamic_library->GetFunction(&XPRSdestroyprob, "XPRSdestroyprob"); + xpress_dynamic_library->GetFunction(&XPRSinit, "XPRSinit"); + xpress_dynamic_library->GetFunction(&XPRSfree, "XPRSfree"); + xpress_dynamic_library->GetFunction(&XPRSgetlicerrmsg, "XPRSgetlicerrmsg"); + xpress_dynamic_library->GetFunction(&XPRSlicense, "XPRSlicense"); + xpress_dynamic_library->GetFunction(&XPRSgetbanner, "XPRSgetbanner"); + xpress_dynamic_library->GetFunction(&XPRSgetversion, "XPRSgetversion"); + xpress_dynamic_library->GetFunction(&XPRSsetdefaultcontrol, "XPRSsetdefaultcontrol"); + xpress_dynamic_library->GetFunction(&XPRSinterrupt, "XPRSinterrupt"); + xpress_dynamic_library->GetFunction(&XPRSsetintcontrol, "XPRSsetintcontrol"); + xpress_dynamic_library->GetFunction(&XPRSsetintcontrol64, "XPRSsetintcontrol64"); + xpress_dynamic_library->GetFunction(&XPRSsetdblcontrol, "XPRSsetdblcontrol"); + xpress_dynamic_library->GetFunction(&XPRSsetstrcontrol, "XPRSsetstrcontrol"); + xpress_dynamic_library->GetFunction(&XPRSgetintcontrol, "XPRSgetintcontrol"); + xpress_dynamic_library->GetFunction(&XPRSgetintcontrol64, "XPRSgetintcontrol64"); + xpress_dynamic_library->GetFunction(&XPRSgetdblcontrol, "XPRSgetdblcontrol"); + xpress_dynamic_library->GetFunction(&XPRSgetstringcontrol, "XPRSgetstringcontrol"); + xpress_dynamic_library->GetFunction(&XPRSgetintattrib, "XPRSgetintattrib"); + xpress_dynamic_library->GetFunction(&XPRSgetdblattrib, "XPRSgetdblattrib"); + xpress_dynamic_library->GetFunction(&XPRSloadlp, "XPRSloadlp"); + xpress_dynamic_library->GetFunction(&XPRSloadlp64, "XPRSloadlp64"); + xpress_dynamic_library->GetFunction(&XPRSgetobj, "XPRSgetobj"); + xpress_dynamic_library->GetFunction(&XPRSgetrhs, "XPRSgetrhs"); + xpress_dynamic_library->GetFunction(&XPRSgetrhsrange, "XPRSgetrhsrange"); + xpress_dynamic_library->GetFunction(&XPRSgetlb, "XPRSgetlb"); + xpress_dynamic_library->GetFunction(&XPRSgetub, "XPRSgetub"); + xpress_dynamic_library->GetFunction(&XPRSgetcoef, "XPRSgetcoef"); + xpress_dynamic_library->GetFunction(&XPRSaddrows, "XPRSaddrows"); + xpress_dynamic_library->GetFunction(&XPRSdelrows, "XPRSdelrows"); + xpress_dynamic_library->GetFunction(&XPRSaddcols, "XPRSaddcols"); + xpress_dynamic_library->GetFunction(&XPRSaddnames, "XPRSaddnames"); + xpress_dynamic_library->GetFunction(&XPRSgetnames, "XPRSgetnames"); + xpress_dynamic_library->GetFunction(&XPRSdelcols, "XPRSdelcols"); + xpress_dynamic_library->GetFunction(&XPRSchgcoltype, "XPRSchgcoltype"); + xpress_dynamic_library->GetFunction(&XPRSloadbasis, "XPRSloadbasis"); + xpress_dynamic_library->GetFunction(&XPRSpostsolve, "XPRSpostsolve"); + xpress_dynamic_library->GetFunction(&XPRSchgobjsense, "XPRSchgobjsense"); + xpress_dynamic_library->GetFunction(&XPRSgetlasterror, "XPRSgetlasterror"); + xpress_dynamic_library->GetFunction(&XPRSgetbasis, "XPRSgetbasis"); + xpress_dynamic_library->GetFunction(&XPRSwriteprob, "XPRSwriteprob"); + xpress_dynamic_library->GetFunction(&XPRSgetrowtype, "XPRSgetrowtype"); + xpress_dynamic_library->GetFunction(&XPRSgetcoltype, "XPRSgetcoltype"); + xpress_dynamic_library->GetFunction(&XPRSchgbounds, "XPRSchgbounds"); + xpress_dynamic_library->GetFunction(&XPRSaddmipsol, "XPRSaddmipsol"); + xpress_dynamic_library->GetFunction(&XPRSgetlpsol, "XPRSgetlpsol"); + xpress_dynamic_library->GetFunction(&XPRSgetmipsol, "XPRSgetmipsol"); + xpress_dynamic_library->GetFunction(&XPRSchgobj, "XPRSchgobj"); + xpress_dynamic_library->GetFunction(&XPRSchgcoef, "XPRSchgcoef"); + xpress_dynamic_library->GetFunction(&XPRSchgmcoef, "XPRSchgmcoef"); + xpress_dynamic_library->GetFunction(&XPRSchgrhs, "XPRSchgrhs"); + xpress_dynamic_library->GetFunction(&XPRSchgrhsrange, "XPRSchgrhsrange"); + xpress_dynamic_library->GetFunction(&XPRSchgrowtype, "XPRSchgrowtype"); + xpress_dynamic_library->GetFunction(&XPRSaddcbintsol, "XPRSaddcbintsol"); + xpress_dynamic_library->GetFunction(&XPRSremovecbintsol, "XPRSremovecbintsol"); + xpress_dynamic_library->GetFunction(&XPRSaddcbmessage, "XPRSaddcbmessage"); + xpress_dynamic_library->GetFunction(&XPRSlpoptimize, "XPRSlpoptimize"); + xpress_dynamic_library->GetFunction(&XPRSmipoptimize, "XPRSmipoptimize"); +} + +void printXpressBanner(bool error) { + char banner[XPRS_MAXBANNERLENGTH]; + XPRSgetbanner(banner); + + if (error) { + LOG(ERROR) << "XpressInterface : Xpress banner :\n" << banner << "\n"; + } else { + LOG(WARNING) << "XpressInterface : Xpress banner :\n" << banner << "\n"; + } +} + +std::vector XpressDynamicLibraryPotentialPaths() { + std::vector potential_paths; + + // Look for libraries pointed by XPRESSDIR first. + const char* xpressdir_from_env = getenv("XPRESSDIR"); + if (xpressdir_from_env != nullptr) { + LOG(INFO) << "Environment variable XPRESSDIR = " << xpressdir_from_env; +#if defined(_MSC_VER) // Windows + potential_paths.push_back( + absl::StrCat(xpressdir_from_env, "\\bin\\xprs.dll")); +#elif defined(__APPLE__) // OS X + potential_paths.push_back( + absl::StrCat(xpressdir_from_env, "/lib/libxprs.dylib")); +#elif defined(__GNUC__) // Linux + potential_paths.push_back( + absl::StrCat(xpressdir_from_env, "/lib/libxprs.so")); +#else + LOG(ERROR) << "OS Not recognized by xpress/environment.cc." + << " You won't be able to use Xpress."; +#endif + } else { + LOG(WARNING) << "Environment variable XPRESSDIR undefined."; + } + + // Search for canonical places. +#if defined(_MSC_VER) // Windows + potential_paths.push_back(absl::StrCat("C:\\xpressmp\\bin\\xprs.dll")); + potential_paths.push_back( + absl::StrCat("C:\\Program Files\\xpressmp\\bin\\xprs.dll")); +#elif defined(__APPLE__) // OS X + potential_paths.push_back( + absl::StrCat("/Library/xpressmp/lib/libxprs.dylib")); +#elif defined(__GNUC__) // Linux + potential_paths.push_back(absl::StrCat("/opt/xpressmp/lib/libxprs.so")); +#else + LOG(ERROR) << "OS Not recognized by xpress/environment.cc." + << " You won't be able to use Xpress."; +#endif + return potential_paths; +} + +absl::Status LoadXpressDynamicLibrary(std::string& xpresspath) { + static std::string xpress_lib_path; + static std::once_flag xpress_loading_done; + static absl::Status xpress_load_status; + static DynamicLibrary xpress_library; + static absl::Mutex mutex; + + absl::MutexLock lock(&mutex); + + std::call_once(xpress_loading_done, []() { + const std::vector canonical_paths = + XpressDynamicLibraryPotentialPaths(); + for (const std::string& path : canonical_paths) { + if (xpress_library.TryToLoad(path)) { + LOG(INFO) << "Found the Xpress library in " << path << "."; + xpress_lib_path.clear(); + std::filesystem::path p(path); + p.remove_filename(); + xpress_lib_path.append(p.string()); + break; + } + } + + if (xpress_library.LibraryIsLoaded()) { + LOG(INFO) << "Loading all Xpress functions"; + LoadXpressFunctions(&xpress_library); + xpress_load_status = absl::OkStatus(); + } else { + xpress_load_status = absl::NotFoundError( + absl::StrCat("Could not find the Xpress shared library. Looked in: [", + absl::StrJoin(canonical_paths, "', '"), + "]. Please check environment variable XPRESSDIR")); + } + }); + xpresspath.clear(); + xpresspath.append(xpress_lib_path); + return xpress_load_status; +} + +void log_message_about_XPRSinit_argument(); +void log_full_license_error(int code, const std::string& xpress_lib_dir); +/** init XPRESS environment */ +bool initXpressEnv(bool verbose, int xpress_oem_license_key) { + std::string xpress_lib_dir; + absl::Status status = LoadXpressDynamicLibrary(xpress_lib_dir); + if (!status.ok()) { + LOG(WARNING) << status << "\n"; + return false; + } + + int code; + // if not an OEM key + if (xpress_oem_license_key == 0) { + if (verbose) { + log_message_about_XPRSinit_argument(); + } + + code = XPRSinit(nullptr); + + if (!code) { + // XPRSbanner informs about Xpress version, options and error messages + if (verbose) { + printXpressBanner(false); + char version[16]; + XPRSgetversion(version); + LOG(WARNING) << "Optimizer version: " << version + << " (OR-Tools was compiled with version " << XPVERSION + << ").\n"; + } + return true; + } else { + log_full_license_error(code, xpress_lib_dir); + return false; + } + } else { + // if OEM key + if (verbose) { + LOG(WARNING) << "XpressInterface : Initialising xpress-MP with OEM key " + << xpress_oem_license_key << "\n"; + } + + int nvalue = 0; + int ierr; + char slicmsg[256] = ""; + char errmsg[256]; + + XPRSlicense(&nvalue, slicmsg); + if (verbose) { + VLOG(0) << "XpressInterface : First message from XPRSLicense : " + << slicmsg << "\n"; + } + + nvalue = xpress_oem_license_key - ((nvalue * nvalue) / 19); + ierr = XPRSlicense(&nvalue, slicmsg); + + if (verbose) { + VLOG(0) << "XpressInterface : Second message from XPRSLicense : " + << slicmsg << "\n"; + } + if (ierr == 16) { + if (verbose) { + VLOG(0) + << "XpressInterface : Optimizer development software detected\n"; + } + } else if (ierr != 0) { + // get the license error message + XPRSgetlicerrmsg(errmsg, 256); + + LOG(ERROR) << "XpressInterface : " << errmsg << "\n"; + return false; + } + + code = XPRSinit(NULL); + + if (!code) { + return true; + } else { + LOG(ERROR) << "XPRSinit returned code : " << code << "\n"; + return false; + } + } +} +void log_full_license_error(int code, const std::string& xpress_lib_dir) { + LOG(WARNING) << "XpressInterface: Xpress found at " << xpress_lib_dir + << "\n"; + char errmsg[256]; + XPRSgetlicerrmsg(errmsg, 256); + + LOG(ERROR) << "XpressInterface : License error : " << errmsg + << " (XPRSinit returned code " << code << "). \n"; + LOG(ERROR) + << "|_Your Xpress installation should have set the env var XPAUTH_PATH" + " to the full path of your licence file\n"; +} +void log_message_about_XPRSinit_argument() { + LOG(WARNING) + << "XpressInterface : Initialising xpress-MP with default parameters"; +} + +bool XpressIsCorrectlyInstalled() { + bool correctlyInstalled = initXpressEnv(false); + if (correctlyInstalled) { + XPRSfree(); + } + return correctlyInstalled; +} + +} // namespace operations_research diff --git a/ortools/xpress/environment.h b/ortools/xpress/environment.h new file mode 100644 index 0000000000..fcbec406c7 --- /dev/null +++ b/ortools/xpress/environment.h @@ -0,0 +1,499 @@ +// Copyright 2019-2023 RTE +// 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. + +// Initial version of this code was provided by RTE + +#ifndef OR_TOOLS_XPRESS_ENVIRONMENT_H +#define OR_TOOLS_XPRESS_ENVIRONMENT_H + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "ortools/base/commandlineflags.h" +#include "ortools/base/dynamic_library.h" +#include "ortools/base/logging.h" + +extern "C" { +typedef struct xo_prob_struct* XPRSprob; +} + +namespace operations_research { + +void printXpressBanner(bool error); + +bool initXpressEnv(bool verbose = true, int xpress_oem_license_key = 0); + +bool XpressIsCorrectlyInstalled(); +// clang-format off +// Force the loading of the xpress dynamic library. It returns true if the +// library was successfully loaded. This method can only be called once. +// Successive calls are no-op. +// +// Note that it does not check if a token license can be grabbed. +absl::Status LoadXpressDynamicLibrary(std::string &xpresspath); + +// The list of #define and extern std::function<> below is generated directly +// from xprs.h via parse_header_xpress.py +// See the top comment on the parse_header_xpress.py file. +// This is the header section +#if defined(_WIN32) +#define XPRSint64 __int64 +#elif defined(__LP64__) || defined(_LP64) || defined(__ILP64__) || defined(_ILP64) +#define XPRSint64 long +#else +#define XPRSint64 long long +#endif + +#if defined(_MSC_VER) +#define XPRS_CC __stdcall +#else +#define XPRS_CC +#endif +/***************************************************************************\ + * values related to XPRSinterrupt * +\***************************************************************************/ +#define XPRS_STOP_NONE 0 +#define XPRS_STOP_TIMELIMIT 1 +#define XPRS_STOP_CTRLC 2 +#define XPRS_STOP_NODELIMIT 3 +#define XPRS_STOP_ITERLIMIT 4 +#define XPRS_STOP_MIPGAP 5 +#define XPRS_STOP_SOLLIMIT 6 +#define XPRS_STOP_GENERICERROR 7 +#define XPRS_STOP_MEMORYERROR 8 +#define XPRS_STOP_USER 9 +#define XPRS_STOP_SOLVECOMPLETE 10 +#define XPRS_STOP_LICENSELOST 11 +#define XPRS_STOP_NUMERICALERROR 13 +/***************************************************************************\ + * values related to Set/GetControl/Attribinfo * +\***************************************************************************/ +#define XPRS_TYPE_NOTDEFINED 0 +#define XPRS_TYPE_INT 1 +#define XPRS_TYPE_INT64 2 +#define XPRS_TYPE_DOUBLE 3 +#define XPRS_TYPE_STRING 4 +/***************************************************************************\ + * values related to NAMESPACES * +\***************************************************************************/ +#define XPRS_NAMES_ROW 1 +#define XPRS_NAMES_COLUMN 2 + + +#define XPRS_PLUSINFINITY 1.0e+20 +#define XPRS_MINUSINFINITY -1.0e+20 +#define XPRS_MAXBANNERLENGTH 512 +#define XPVERSION 41 +#define XPRS_MPSRHSNAME 6001 +#define XPRS_MPSOBJNAME 6002 +#define XPRS_MPSRANGENAME 6003 +#define XPRS_MPSBOUNDNAME 6004 +#define XPRS_OUTPUTMASK 6005 +#define XPRS_TUNERMETHODFILE 6017 +#define XPRS_TUNEROUTPUTPATH 6018 +#define XPRS_TUNERSESSIONNAME 6019 +#define XPRS_COMPUTEEXECSERVICE 6022 +#define XPRS_MAXCUTTIME 8149 +#define XPRS_MAXSTALLTIME 8443 +#define XPRS_TUNERMAXTIME 8364 +#define XPRS_MATRIXTOL 7001 +#define XPRS_PIVOTTOL 7002 +#define XPRS_FEASTOL 7003 +#define XPRS_OUTPUTTOL 7004 +#define XPRS_SOSREFTOL 7005 +#define XPRS_OPTIMALITYTOL 7006 +#define XPRS_ETATOL 7007 +#define XPRS_RELPIVOTTOL 7008 +#define XPRS_MIPTOL 7009 +#define XPRS_MIPTOLTARGET 7010 +#define XPRS_BARPERTURB 7011 +#define XPRS_MIPADDCUTOFF 7012 +#define XPRS_MIPABSCUTOFF 7013 +#define XPRS_MIPRELCUTOFF 7014 +#define XPRS_PSEUDOCOST 7015 +#define XPRS_PENALTY 7016 +#define XPRS_BIGM 7018 +#define XPRS_MIPABSSTOP 7019 +#define XPRS_MIPRELSTOP 7020 +#define XPRS_CROSSOVERACCURACYTOL 7023 +#define XPRS_PRIMALPERTURB 7024 +#define XPRS_DUALPERTURB 7025 +#define XPRS_BAROBJSCALE 7026 +#define XPRS_BARRHSSCALE 7027 +#define XPRS_CHOLESKYTOL 7032 +#define XPRS_BARGAPSTOP 7033 +#define XPRS_BARDUALSTOP 7034 +#define XPRS_BARPRIMALSTOP 7035 +#define XPRS_BARSTEPSTOP 7036 +#define XPRS_ELIMTOL 7042 +#define XPRS_MARKOWITZTOL 7047 +#define XPRS_MIPABSGAPNOTIFY 7064 +#define XPRS_MIPRELGAPNOTIFY 7065 +#define XPRS_BARLARGEBOUND 7067 +#define XPRS_PPFACTOR 7069 +#define XPRS_REPAIRINDEFINITEQMAX 7071 +#define XPRS_BARGAPTARGET 7073 +#define XPRS_DUMMYCONTROL 7075 +#define XPRS_BARSTARTWEIGHT 7076 +#define XPRS_BARFREESCALE 7077 +#define XPRS_SBEFFORT 7086 +#define XPRS_HEURDIVERANDOMIZE 7089 +#define XPRS_HEURSEARCHEFFORT 7090 +#define XPRS_CUTFACTOR 7091 +#define XPRS_EIGENVALUETOL 7097 +#define XPRS_INDLINBIGM 7099 +#define XPRS_TREEMEMORYSAVINGTARGET 7100 +#define XPRS_INDPRELINBIGM 7102 +#define XPRS_RELAXTREEMEMORYLIMIT 7105 +#define XPRS_MIPABSGAPNOTIFYOBJ 7108 +#define XPRS_MIPABSGAPNOTIFYBOUND 7109 +#define XPRS_PRESOLVEMAXGROW 7110 +#define XPRS_HEURSEARCHTARGETSIZE 7112 +#define XPRS_CROSSOVERRELPIVOTTOL 7113 +#define XPRS_CROSSOVERRELPIVOTTOLSAFE 7114 +#define XPRS_DETLOGFREQ 7116 +#define XPRS_MAXIMPLIEDBOUND 7120 +#define XPRS_FEASTOLTARGET 7121 +#define XPRS_OPTIMALITYTOLTARGET 7122 +#define XPRS_PRECOMPONENTSEFFORT 7124 +#define XPRS_LPLOGDELAY 7127 +#define XPRS_HEURDIVEITERLIMIT 7128 +#define XPRS_BARKERNEL 7130 +#define XPRS_FEASTOLPERTURB 7132 +#define XPRS_CROSSOVERFEASWEIGHT 7133 +#define XPRS_LUPIVOTTOL 7139 +#define XPRS_MIPRESTARTGAPTHRESHOLD 7140 +#define XPRS_NODEPROBINGEFFORT 7141 +#define XPRS_INPUTTOL 7143 +#define XPRS_MIPRESTARTFACTOR 7145 +#define XPRS_BAROBJPERTURB 7146 +#define XPRS_CPIALPHA 7149 +#define XPRS_GLOBALBOUNDINGBOX 7154 +#define XPRS_TIMELIMIT 7158 +#define XPRS_SOLTIMELIMIT 7159 +#define XPRS_REPAIRINFEASTIMELIMIT 7160 +#define XPRS_EXTRAROWS 8004 +#define XPRS_EXTRACOLS 8005 +#define XPRS_LPITERLIMIT 8007 +#define XPRS_LPLOG 8009 +#define XPRS_SCALING 8010 +#define XPRS_PRESOLVE 8011 +#define XPRS_CRASH 8012 +#define XPRS_PRICINGALG 8013 +#define XPRS_INVERTFREQ 8014 +#define XPRS_INVERTMIN 8015 +#define XPRS_MAXNODE 8018 +#define XPRS_MAXTIME 8020 +#define XPRS_MAXMIPSOL 8021 +#define XPRS_SIFTPASSES 8022 +#define XPRS_DEFAULTALG 8023 +#define XPRS_VARSELECTION 8025 +#define XPRS_NODESELECTION 8026 +#define XPRS_BACKTRACK 8027 +#define XPRS_MIPLOG 8028 +#define XPRS_KEEPNROWS 8030 +#define XPRS_MPSECHO 8032 +#define XPRS_MAXPAGELINES 8034 +#define XPRS_OUTPUTLOG 8035 +#define XPRS_BARSOLUTION 8038 +#define XPRS_CACHESIZE 8043 +#define XPRS_CROSSOVER 8044 +#define XPRS_BARITERLIMIT 8045 +#define XPRS_CHOLESKYALG 8046 +#define XPRS_BAROUTPUT 8047 +#define XPRS_EXTRAMIPENTS 8051 +#define XPRS_REFACTOR 8052 +#define XPRS_BARTHREADS 8053 +#define XPRS_KEEPBASIS 8054 +#define XPRS_CROSSOVEROPS 8060 +#define XPRS_VERSION 8061 +#define XPRS_CROSSOVERTHREADS 8065 +#define XPRS_BIGMMETHOD 8068 +#define XPRS_MPSNAMELENGTH 8071 +#define XPRS_ELIMFILLIN 8073 +#define XPRS_PRESOLVEOPS 8077 +#define XPRS_MIPPRESOLVE 8078 +#define XPRS_MIPTHREADS 8079 +#define XPRS_BARORDER 8080 +#define XPRS_BREADTHFIRST 8082 +#define XPRS_AUTOPERTURB 8084 +#define XPRS_DENSECOLLIMIT 8086 +#define XPRS_CALLBACKFROMMASTERTHREAD 8090 +#define XPRS_MAXMCOEFFBUFFERELEMS 8091 +#define XPRS_REFINEOPS 8093 +#define XPRS_LPREFINEITERLIMIT 8094 +#define XPRS_MIPREFINEITERLIMIT 8095 +#define XPRS_DUALIZEOPS 8097 +#define XPRS_CROSSOVERITERLIMIT 8104 +#define XPRS_PREBASISRED 8106 +#define XPRS_PRESORT 8107 +#define XPRS_PREPERMUTE 8108 +#define XPRS_PREPERMUTESEED 8109 +#define XPRS_MAXMEMORYSOFT 8112 +#define XPRS_CUTFREQ 8116 +#define XPRS_SYMSELECT 8117 +#define XPRS_SYMMETRY 8118 +#define XPRS_MAXMEMORYHARD 8119 +#define XPRS_MIQCPALG 8125 +#define XPRS_QCCUTS 8126 +#define XPRS_QCROOTALG 8127 +#define XPRS_PRECONVERTSEPARABLE 8128 +#define XPRS_ALGAFTERNETWORK 8129 +#define XPRS_TRACE 8130 +#define XPRS_MAXIIS 8131 +#define XPRS_CPUTIME 8133 +#define XPRS_COVERCUTS 8134 +#define XPRS_GOMCUTS 8135 +#define XPRS_LPFOLDING 8136 +#define XPRS_MPSFORMAT 8137 +#define XPRS_CUTSTRATEGY 8138 +#define XPRS_CUTDEPTH 8139 +#define XPRS_TREECOVERCUTS 8140 +#define XPRS_TREEGOMCUTS 8141 +#define XPRS_CUTSELECT 8142 +#define XPRS_TREECUTSELECT 8143 +#define XPRS_DUALIZE 8144 +#define XPRS_DUALGRADIENT 8145 +#define XPRS_SBITERLIMIT 8146 +#define XPRS_SBBEST 8147 +#define XPRS_BARINDEFLIMIT 8153 +#define XPRS_HEURFREQ 8155 +#define XPRS_HEURDEPTH 8156 +#define XPRS_HEURMAXSOL 8157 +#define XPRS_HEURNODES 8158 +#define XPRS_LNPBEST 8160 +#define XPRS_LNPITERLIMIT 8161 +#define XPRS_BRANCHCHOICE 8162 +#define XPRS_BARREGULARIZE 8163 +#define XPRS_SBSELECT 8164 +#define XPRS_LOCALCHOICE 8170 +#define XPRS_LOCALBACKTRACK 8171 +#define XPRS_DUALSTRATEGY 8174 +#define XPRS_L1CACHE 8175 +#define XPRS_HEURDIVESTRATEGY 8177 +#define XPRS_HEURSELECT 8178 +#define XPRS_BARSTART 8180 +#define XPRS_PRESOLVEPASSES 8183 +#define XPRS_BARNUMSTABILITY 8186 +#define XPRS_BARORDERTHREADS 8187 +#define XPRS_EXTRASETS 8190 +#define XPRS_FEASIBILITYPUMP 8193 +#define XPRS_PRECOEFELIM 8194 +#define XPRS_PREDOMCOL 8195 +#define XPRS_HEURSEARCHFREQ 8196 +#define XPRS_HEURDIVESPEEDUP 8197 +#define XPRS_SBESTIMATE 8198 +#define XPRS_BARCORES 8202 +#define XPRS_MAXCHECKSONMAXTIME 8203 +#define XPRS_MAXCHECKSONMAXCUTTIME 8204 +#define XPRS_HISTORYCOSTS 8206 +#define XPRS_ALGAFTERCROSSOVER 8208 +#define XPRS_MUTEXCALLBACKS 8210 +#define XPRS_BARCRASH 8211 +#define XPRS_HEURDIVESOFTROUNDING 8215 +#define XPRS_HEURSEARCHROOTSELECT 8216 +#define XPRS_HEURSEARCHTREESELECT 8217 +#define XPRS_MPS18COMPATIBLE 8223 +#define XPRS_ROOTPRESOLVE 8224 +#define XPRS_CROSSOVERDRP 8227 +#define XPRS_FORCEOUTPUT 8229 +#define XPRS_PRIMALOPS 8231 +#define XPRS_DETERMINISTIC 8232 +#define XPRS_PREPROBING 8238 +#define XPRS_TREEMEMORYLIMIT 8242 +#define XPRS_TREECOMPRESSION 8243 +#define XPRS_TREEDIAGNOSTICS 8244 +#define XPRS_MAXTREEFILESIZE 8245 +#define XPRS_PRECLIQUESTRATEGY 8247 +#define XPRS_REPAIRINFEASMAXTIME 8250 +#define XPRS_IFCHECKCONVEXITY 8251 +#define XPRS_PRIMALUNSHIFT 8252 +#define XPRS_REPAIRINDEFINITEQ 8254 +#define XPRS_MIPRAMPUP 8255 +#define XPRS_MAXLOCALBACKTRACK 8257 +#define XPRS_USERSOLHEURISTIC 8258 +#define XPRS_FORCEPARALLELDUAL 8265 +#define XPRS_BACKTRACKTIE 8266 +#define XPRS_BRANCHDISJ 8267 +#define XPRS_MIPFRACREDUCE 8270 +#define XPRS_CONCURRENTTHREADS 8274 +#define XPRS_MAXSCALEFACTOR 8275 +#define XPRS_HEURTHREADS 8276 +#define XPRS_THREADS 8278 +#define XPRS_HEURBEFORELP 8280 +#define XPRS_PREDOMROW 8281 +#define XPRS_BRANCHSTRUCTURAL 8282 +#define XPRS_QUADRATICUNSHIFT 8284 +#define XPRS_BARPRESOLVEOPS 8286 +#define XPRS_QSIMPLEXOPS 8288 +#define XPRS_MIPRESTART 8290 +#define XPRS_CONFLICTCUTS 8292 +#define XPRS_PREPROTECTDUAL 8293 +#define XPRS_CORESPERCPU 8296 +#define XPRS_RESOURCESTRATEGY 8297 +#define XPRS_CLAMPING 8301 +#define XPRS_SLEEPONTHREADWAIT 8302 +#define XPRS_PREDUPROW 8307 +#define XPRS_CPUPLATFORM 8312 +#define XPRS_BARALG 8315 +#define XPRS_SIFTING 8319 +#define XPRS_LPLOGSTYLE 8326 +#define XPRS_RANDOMSEED 8328 +#define XPRS_TREEQCCUTS 8331 +#define XPRS_PRELINDEP 8333 +#define XPRS_DUALTHREADS 8334 +#define XPRS_PREOBJCUTDETECT 8336 +#define XPRS_PREBNDREDQUAD 8337 +#define XPRS_PREBNDREDCONE 8338 +#define XPRS_PRECOMPONENTS 8339 +#define XPRS_MAXMIPTASKS 8347 +#define XPRS_MIPTERMINATIONMETHOD 8348 +#define XPRS_PRECONEDECOMP 8349 +#define XPRS_HEURFORCESPECIALOBJ 8350 +#define XPRS_HEURSEARCHROOTCUTFREQ 8351 +#define XPRS_PREELIMQUAD 8353 +#define XPRS_PREIMPLICATIONS 8356 +#define XPRS_TUNERMODE 8359 +#define XPRS_TUNERMETHOD 8360 +#define XPRS_TUNERTARGET 8362 +#define XPRS_TUNERTHREADS 8363 +#define XPRS_TUNERHISTORY 8365 +#define XPRS_TUNERPERMUTE 8366 +#define XPRS_TUNERVERBOSE 8370 +#define XPRS_TUNEROUTPUT 8372 +#define XPRS_PREANALYTICCENTER 8374 +#define XPRS_NETCUTS 8382 +#define XPRS_LPFLAGS 8385 +#define XPRS_MIPKAPPAFREQ 8386 +#define XPRS_OBJSCALEFACTOR 8387 +#define XPRS_TREEFILELOGINTERVAL 8389 +#define XPRS_IGNORECONTAINERCPULIMIT 8390 +#define XPRS_IGNORECONTAINERMEMORYLIMIT 8391 +#define XPRS_MIPDUALREDUCTIONS 8392 +#define XPRS_GENCONSDUALREDUCTIONS 8395 +#define XPRS_PWLDUALREDUCTIONS 8396 +#define XPRS_BARFAILITERLIMIT 8398 +#define XPRS_AUTOSCALING 8406 +#define XPRS_GENCONSABSTRANSFORMATION 8408 +#define XPRS_COMPUTEJOBPRIORITY 8409 +#define XPRS_PREFOLDING 8410 +#define XPRS_NETSTALLLIMIT 8412 +#define XPRS_SERIALIZEPREINTSOL 8413 +#define XPRS_NUMERICALEMPHASIS 8416 +#define XPRS_PWLNONCONVEXTRANSFORMATION 8420 +#define XPRS_MIPCOMPONENTS 8421 +#define XPRS_MIPCONCURRENTNODES 8422 +#define XPRS_MIPCONCURRENTSOLVES 8423 +#define XPRS_OUTPUTCONTROLS 8424 +#define XPRS_SIFTSWITCH 8425 +#define XPRS_HEUREMPHASIS 8427 +#define XPRS_COMPUTEMATX 8428 +#define XPRS_COMPUTEMATX_IIS 8429 +#define XPRS_COMPUTEMATX_IISMAXTIME 8430 +#define XPRS_BARREFITER 8431 +#define XPRS_COMPUTELOG 8434 +#define XPRS_SIFTPRESOLVEOPS 8435 +#define XPRS_CHECKINPUTDATA 8436 +#define XPRS_ESCAPENAMES 8440 +#define XPRS_IOTIMEOUT 8442 +#define XPRS_AUTOCUTTING 8446 +#define XPRS_CALLBACKCHECKTIMEDELAY 8451 +#define XPRS_MULTIOBJOPS 8457 +#define XPRS_MULTIOBJLOG 8458 +#define XPRS_GLOBALSPATIALBRANCHIFPREFERORIG 8465 +#define XPRS_PRECONFIGURATION 8470 +#define XPRS_FEASIBILITYJUMP 8471 +#define XPRS_EXTRAELEMS 8006 +#define XPRS_EXTRASETELEMS 8191 +#define XPRS_LPOBJVAL 2001 +#define XPRS_MIPOBJVAL 2003 +#define XPRS_BESTBOUND 2004 +#define XPRS_OBJRHS 2005 +#define XPRS_OBJSENSE 2008 +#define XPRS_ROWS 1001 +#define XPRS_SIMPLEXITER 1009 +#define XPRS_LPSTATUS 1010 +#define XPRS_MIPSTATUS 1011 +#define XPRS_NODES 1013 +#define XPRS_COLS 1018 +#define XPRS_LP_OPTIMAL 1 +#define XPRS_LP_INFEAS 2 +#define XPRS_LP_UNBOUNDED 5 +#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 +extern std::function XPRScreateprob; +extern std::function XPRSdestroyprob; +extern std::function XPRSinit; +extern std::function XPRSfree; +extern std::function XPRSgetlicerrmsg; +extern std::function XPRSlicense; +extern std::function XPRSgetbanner; +extern std::function XPRSgetversion; +extern std::function XPRSsetdefaultcontrol; +extern std::function XPRSinterrupt; +extern std::function XPRSsetintcontrol; +extern std::function XPRSsetintcontrol64; +extern std::function XPRSsetdblcontrol; +extern std::function XPRSsetstrcontrol; +extern std::function XPRSgetintcontrol; +extern std::function XPRSgetintcontrol64; +extern std::function XPRSgetdblcontrol; +extern std::function XPRSgetstringcontrol; +extern std::function XPRSgetintattrib; +extern std::function XPRSgetdblattrib; +extern std::function XPRSgetcontrolinfo; +extern std::function XPRSloadlp; +extern std::function XPRSloadlp64; +extern std::function XPRSgetobj; +extern std::function XPRSgetrhs; +extern std::function XPRSgetrhsrange; +extern std::function XPRSgetlb; +extern std::function XPRSgetub; +extern std::function XPRSgetcoef; +extern std::function XPRSaddrows; +extern std::function XPRSdelrows; +extern std::function XPRSaddcols; +extern std::function XPRSaddnames; +extern std::function XPRSgetnames; +extern std::function XPRSdelcols; +extern std::function XPRSchgcoltype; +extern std::function XPRSloadbasis; +extern std::function XPRSpostsolve; +extern std::function XPRSchgobjsense; +extern std::function XPRSgetlasterror; +extern std::function XPRSgetbasis; +extern std::function XPRSwriteprob; +extern std::function XPRSgetrowtype; +extern std::function XPRSgetcoltype; +extern std::function XPRSchgbounds; +extern std::function XPRSaddmipsol; +extern std::function XPRSgetlpsol; +extern std::function XPRSgetmipsol; +extern std::function XPRSchgobj; +extern std::function XPRSchgcoef; +extern std::function XPRSchgmcoef; +extern std::function XPRSchgrhs; +extern std::function XPRSchgrhsrange; +extern std::function XPRSchgrowtype; +extern std::function XPRSaddcbintsol; +extern std::function XPRSremovecbintsol; +extern std::function XPRSaddcbmessage; +extern std::function XPRSlpoptimize; +extern std::function XPRSmipoptimize; + +} // namespace operations_research + +#endif // OR_TOOLS_XPRESS_ENVIRONMENT_H