From 3c4a76e68f0178a5ea85b57a71dbcf75a179dc4d Mon Sep 17 00:00:00 2001 From: Andrea Sgattoni Date: Tue, 17 Oct 2023 10:09:20 +0200 Subject: [PATCH] update xpress interface with dynamic load --- examples/cpp/xpress_use.cc | 103 ++ examples/dotnet/cslinearprogramming.cs | 2 + examples/java/LinearProgramming.java | 2 + examples/python/callback_xpress.py | 80 ++ ortools/base/dynamic_library.h | 15 +- ortools/linear_solver/MPSWriteError.h | 25 + ortools/linear_solver/java/linear_solver.i | 37 + ortools/linear_solver/linear_solver.cc | 6 +- ortools/linear_solver/linear_solver.h | 24 + ortools/linear_solver/python/linear_solver.i | 43 + ortools/linear_solver/xpress_interface.cc | 1105 ++++++++++++++---- ortools/xpress/BUILD.bazel | 22 + ortools/xpress/CMakeLists.txt | 25 + ortools/xpress/environment.cc | 366 ++++++ ortools/xpress/environment.h | 492 ++++++++ ortools/xpress/parse_header_xpress.py | 330 ++++++ 16 files changed, 2463 insertions(+), 214 deletions(-) create mode 100644 examples/cpp/xpress_use.cc create mode 100644 examples/python/callback_xpress.py create mode 100644 ortools/linear_solver/MPSWriteError.h create mode 100644 ortools/xpress/BUILD.bazel create mode 100644 ortools/xpress/CMakeLists.txt create mode 100644 ortools/xpress/environment.cc create mode 100644 ortools/xpress/environment.h create mode 100644 ortools/xpress/parse_header_xpress.py diff --git a/examples/cpp/xpress_use.cc b/examples/cpp/xpress_use.cc new file mode 100644 index 0000000000..1559de4556 --- /dev/null +++ b/examples/cpp/xpress_use.cc @@ -0,0 +1,103 @@ +// 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(INFO) << "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 = solver->infinity(); + MPVariable* const x1 = solver->MakeIntVar(0, 5, "x1"); + MPVariable* const 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(INFO) << "Optimal solution found: obj=" << objective->Value(); + } +} + + +int main(int argc, char** argv) { + absl::SetFlag(&FLAGS_logtostderr, true); + google::InitGoogleLogging(argv[0]); + absl::ParseCommandLine(argc, argv); + for (bool solveAsMip: {true, false}) { + for (bool useFactory: {true, false}) { + useXpressSolver(solveAsMip, useFactory); + } + } + return EXIT_SUCCESS; +} diff --git a/examples/dotnet/cslinearprogramming.cs b/examples/dotnet/cslinearprogramming.cs index c3f2188db0..8cffe1cb0a 100644 --- a/examples/dotnet/cslinearprogramming.cs +++ b/examples/dotnet/cslinearprogramming.cs @@ -160,9 +160,11 @@ public class CsLinearProgramming RunLinearProgrammingExample("GLOP"); RunLinearProgrammingExample("GLPK_LP"); RunLinearProgrammingExample("CLP"); + RunLinearProgrammingExample("xpress_lp"); RunLinearProgrammingExampleNaturalApi("GLOP", true); RunLinearProgrammingExampleNaturalApi("GLPK_LP", false); RunLinearProgrammingExampleNaturalApi("CLP", false); + RunLinearProgrammingExampleNaturalApi("xpress_lp", false); } } diff --git a/examples/java/LinearProgramming.java b/examples/java/LinearProgramming.java index 8457d77435..d2cb9fdaa4 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/callback_xpress.py b/examples/python/callback_xpress.py new file mode 100644 index 0000000000..7ac4bc71f8 --- /dev/null +++ b/examples/python/callback_xpress.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# Copyright 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. +"""MIP example/test that shows how to use the callback API.""" + +from ortools.linear_solver import pywraplp +from ortools.linear_solver.pywraplp import MPCallbackContext, MPCallback +import random +import unittest + + +class MyMPCallback(MPCallback): + def __init__(self, mp_solver: pywraplp.Solver): + super().__init__(False, False) + self._mp_solver_ = mp_solver + self._solutions_ = 0 + self._last_var_values_ = [0] * len(mp_solver.variables()) + + def RunCallback(self, ctx: MPCallbackContext): + self._solutions_ += 1 + for i in range(0, len(self._mp_solver_.variables())) : + self._last_var_values_[i] = ctx.VariableValue(self._mp_solver_.variable(i)) + + +class TestSiriusXpress(unittest.TestCase): + def test_callback(self): + """Builds a large MIP that is difficult to solve, in order for us to have time to intercept non-optimal + feasible solutions using callback""" + solver = pywraplp.Solver.CreateSolver('XPRESS_MIXED_INTEGER_PROGRAMMING') + n_vars = 30 + max_time = 30 + if not solver: + return + + random.seed(123) + + objective = solver.Objective() + objective.SetMaximization() + + for i in range(0, n_vars): + x = solver.IntVar(-random.random() * 200, random.random() * 200, 'x_' + str(i)) + objective.SetCoefficient(x, random.random() * 200 - 100) + if i == 0: + continue + rand1 = -random.random() * 2000 + rand2 = random.random() * 2000 + c = solver.Constraint(min(rand1, rand2), max(rand1, rand2)) + c.SetCoefficient(x, random.random() * 200 - 100) + for j in range(0, i): + c.SetCoefficient(solver.variable(j), random.random() * 200 - 100) + + solver.SetSolverSpecificParametersAsString("PRESOLVE 0 MAXTIME " + str(max_time)) + solver.EnableOutput() + + cb = MyMPCallback(solver) + solver.SetCallback(cb) + + solver.Solve() + + # 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) + self.assertTrue(cb._solutions_ > 5) + # Test that the last solution intercepted by callback is the same as the optimal one retained + for i in range(0, len(solver.variables())): + self.assertAlmostEqual(cb._last_var_values_[i], solver.variable(i).SolutionValue()) + + +if __name__ == '__main__': + unittest.main() + diff --git a/ortools/base/dynamic_library.h b/ortools/base/dynamic_library.h index 33a4a75157..0421944098 100644 --- a/ortools/base/dynamic_library.h +++ b/ortools/base/dynamic_library.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "ortools/base/logging.h" @@ -28,6 +29,7 @@ #endif class DynamicLibrary { +static constexpr size_t kMaxFunctionsNotFound = 10; public: DynamicLibrary() : library_handle_(nullptr) {} @@ -55,6 +57,10 @@ class DynamicLibrary { bool LibraryIsLoaded() const { return library_handle_ != nullptr; } + const std::vector& FunctionsNotFound() const { + return functions_not_found_; + } + template std::function GetFunction(const char* function_name) { const void* function_address = @@ -64,10 +70,10 @@ class DynamicLibrary { #else dlsym(library_handle_, function_name); #endif - - CHECK(function_address != nullptr) - << "Error: could not find function " << std::string(function_name) - << " in " << library_name_; + // We don't really need the full list of missing functions, + // just a few are enough. + if (!function_address && functions_not_found_.size() < kMaxFunctionsNotFound) + functions_not_found_.push_back(function_name); return TypeParser::CreateFunction(function_address); } @@ -91,6 +97,7 @@ class DynamicLibrary { private: void* library_handle_ = nullptr; std::string library_name_; + std::vector functions_not_found_; template struct TypeParser {}; diff --git a/ortools/linear_solver/MPSWriteError.h b/ortools/linear_solver/MPSWriteError.h new file mode 100644 index 0000000000..9fc0e2ca4d --- /dev/null +++ b/ortools/linear_solver/MPSWriteError.h @@ -0,0 +1,25 @@ +// Copyright 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 ORTOOLS_MPSWRITEERROR_H +#define ORTOOLS_MPSWRITEERROR_H +#include +namespace operations_research { +class MPSWriteError : public std::runtime_error { + public: + MPSWriteError(const std::string& message) : std::runtime_error(message) {} +}; +} // namespace operations_research +#endif // ORTOOLS_MPSWRITEERROR_H diff --git a/ortools/linear_solver/java/linear_solver.i b/ortools/linear_solver/java/linear_solver.i index 0afd3114de..bc5b758414 100644 --- a/ortools/linear_solver/java/linear_solver.i +++ b/ortools/linear_solver/java/linear_solver.i @@ -46,6 +46,9 @@ class MPModelRequest; class MPSolutionResponse; } // namespace operations_research +// cross-language polymorphism should be enabled to support MPCallback feature +%module(directors="1") operations_research; + %{ #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/model_exporter.h" @@ -390,6 +393,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; @@ -511,6 +515,39 @@ PROTO2_RETURN( %rename (ShowUnusedVariables) operations_research::MPModelExportOptions::show_unused_variables; %rename (MaxLineLength) operations_research::MPModelExportOptions::max_line_length; +// Expose the MPCallback & MPCallbackContext APIs +// Enable cross-language polymorphism for MPCallback virtual class +%feature("director") operations_research::MPCallback; +%unignore operations_research::MPCallback; +%unignore operations_research::MPCallback::MPCallback; +%unignore operations_research::MPCallback::~MPCallback; +%rename (runCallback) operations_research::MPCallback::RunCallback; +%rename (mightAddCuts) operations_research::MPCallback::might_add_cuts; +%rename (mightAddLazyConstraints) operations_research::MPCallback::might_add_lazy_constraints; +%unignore operations_research::MPCallbackContext; +%unignore operations_research::MPCallbackContext::MPCallbackContext; +%unignore operations_research::MPCallbackContext::~MPCallbackContext; +%unignore operations_research::MPCallbackEvent; +%rename (UNKNOWN) operations_research::MPCallbackEvent::kUnknown; +%rename (POLLING) operations_research::MPCallbackEvent::kPolling; +%rename (PRESOLVE) operations_research::MPCallbackEvent::kPresolve; +%rename (SIMPLEX) operations_research::MPCallbackEvent::kSimplex; +%rename (MIP) operations_research::MPCallbackEvent::kMip; +%rename (MIP_SOLUTION) operations_research::MPCallbackEvent::kMipSolution; +%rename (MIP_NODE) operations_research::MPCallbackEvent::kMipNode; +%rename (BARRIER) operations_research::MPCallbackEvent::kBarrier; +%rename (MESSAGE) operations_research::MPCallbackEvent::kMessage; +%rename (MULTI_OBJ) operations_research::MPCallbackContext::MPCallbackEvent::kMultiObj; +%rename (event) operations_research::MPCallbackContext::Event; +%rename (canQueryVariableValues) operations_research::MPCallbackContext::CanQueryVariableValues; +%rename (variableValue) operations_research::MPCallbackContext::VariableValue; +%rename (addCut) operations_research::MPCallbackContext::AddCut; +%rename (addLazyConstraint) operations_research::MPCallbackContext::AddLazyConstraint; +%rename (suggestSolution) operations_research::MPCallbackContext::SuggestSolution; +%rename (numExploredNodes) operations_research::MPCallbackContext::NumExploredNodes; +%rename (setCallback) operations_research::MPSolver::SetCallback; + +%include "ortools/linear_solver/linear_solver_callback.h" %include "ortools/linear_solver/linear_solver.h" %include "ortools/linear_solver/model_exporter.h" diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index 6e7d7c9a16..db99012a66 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -392,10 +392,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) { @@ -488,6 +487,7 @@ MPSolver::MPSolver(const std::string& name, MPSolver::~MPSolver() { Clear(); } extern bool GurobiIsCorrectlyInstalled(); +extern bool XpressIsCorrectlyInstalled(); // static bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { @@ -534,7 +534,7 @@ bool MPSolver::SupportsProblemType(OptimizationProblemType problem_type) { #endif if (problem_type == XPRESS_MIXED_INTEGER_PROGRAMMING || problem_type == XPRESS_LINEAR_PROGRAMMING) { - return true; + return XpressIsCorrectlyInstalled(); } return false; diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index 52be65a3f9..90178ef4a3 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -720,6 +720,12 @@ class MPSolver { const std::vector& variable_statuses, const std::vector& constraint_statuses); + void SetStartingLpBasisInt(const std::vector& variable_statuses, + const std::vector& constraint_statuses); + + void GetFinalLpBasisInt(std::vector& variable_statuses, + std::vector& constraint_statuses); + /** * Infinity. * @@ -1788,6 +1794,24 @@ class MPSolverInterface { LOG(FATAL) << "Not supported by this solver."; } + // See MPSolver::SetStartingLpBasis(). + // Do not convert to MPSolver::BasisStatus, use integers + // Sometimes, converting to MPSolver::BasisStatus leads to a loss of information + // For that reason, we give the possibiity to recover the "raw" basis + virtual void SetStartingLpBasisInt( + const std::vector& /* variable_statuses */, + const std::vector& /* constraint_statuses */) { + LOG(FATAL) << "Not supported by this solver."; + } + + // See MPSolver::SetStartingLpBasis(). + // Do not convert to MPSolver::BasisStatus, use integers + virtual void GetFinalLpBasisInt( + std::vector& /* variable_statuses */, + std::vector& /* constraint_statuses */) { + LOG(FATAL) << "Not supported by this solver."; + } + virtual double infinity() { return std::numeric_limits::infinity(); } virtual bool InterruptSolve() { return false; } diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index 83a639a254..639fd09a31 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -47,6 +47,9 @@ class MPSolutionResponse; class IISResponse; } // namespace operations_research +// cross-language polymorphism should be enabled to support MPCallback feature +%module(directors="1") operations_research; + %{ #include "ortools/linear_solver/linear_solver.h" #include "ortools/linear_solver/model_exporter.h" @@ -275,6 +278,12 @@ PY_CONVERT(MPConstraint); PY_CONVERT_HELPER_PTR(MPVariable); PY_CONVERT(MPVariable); +PY_CONVERT_HELPER_PTR(MPCallback); +PY_CONVERT(MPCallback); + +PY_CONVERT_HELPER_PTR(MPCallbackContext); +PY_CONVERT(MPCallbackContext); + %ignoreall %unignore operations_research; @@ -366,6 +375,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; @@ -493,6 +503,39 @@ PY_CONVERT(MPVariable); // Expose the model validator. %rename (FindErrorInModelProto) operations_research::FindErrorInMPModelProto; +// Expose the MPCallback & MPCallbackContext APIs +// Enable cross-language polymorphism for MPCallback virtual class +%feature("director") operations_research::MPCallback; +%unignore operations_research::MPCallback; +%unignore operations_research::MPCallbackContext; +%unignore operations_research::MPCallback::MPCallback; +%unignore operations_research::MPCallback::~MPCallback; +%unignore operations_research::MPCallback::RunCallback; +%unignore operations_research::MPCallback::might_add_cuts; +%unignore operations_research::MPCallback::might_add_lazy_constraints; +%unignore operations_research::MPCallbackContext::MPCallbackContext; +%unignore operations_research::MPCallbackContext::~MPCallbackContext; +%unignore operations_research::MPCallbackEvent; +%rename (UNKNOWN) operations_research::MPCallbackEvent::kUnknown; +%rename (POLLING) operations_research::MPCallbackEvent::kPolling; +%rename (PRESOLVE) operations_research::MPCallbackEvent::kPresolve; +%rename (SIMPLEX) operations_research::MPCallbackEvent::kSimplex; +%rename (MIP) operations_research::MPCallbackEvent::kMip; +%rename (MIP_SOLUTION) operations_research::MPCallbackEvent::kMipSolution; +%rename (MIP_NODE) operations_research::MPCallbackEvent::kMipNode; +%rename (BARRIER) operations_research::MPCallbackEvent::kBarrier; +%rename (MESSAGE) operations_research::MPCallbackEvent::kMessage; +%rename (MULTI_OBJ) operations_research::MPCallbackContext::MPCallbackEvent::kMultiObj; +%unignore operations_research::MPCallbackContext::Event; +%unignore operations_research::MPCallbackContext::CanQueryVariableValues; +%unignore operations_research::MPCallbackContext::VariableValue; +%unignore operations_research::MPCallbackContext::AddCut; +%unignore operations_research::MPCallbackContext::AddLazyConstraint; +%unignore operations_research::MPCallbackContext::SuggestSolution; +%unignore operations_research::MPCallbackContext::NumExploredNodes; +%unignore operations_research::MPSolver::SetCallback; + +%include "ortools/linear_solver/linear_solver_callback.h" %include "ortools/linear_solver/linear_solver.h" %include "ortools/linear_solver/model_exporter.h" %include "ortools/linear_solver/model_exporter_swig_helper.h" diff --git a/ortools/linear_solver/xpress_interface.cc b/ortools/linear_solver/xpress_interface.cc index db307ab29f..4c0a57ffcc 100644 --- a/ortools/linear_solver/xpress_interface.cc +++ b/ortools/linear_solver/xpress_interface.cc @@ -15,56 +15,42 @@ #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/base/types.h" +#include "ortools/linear_solver/MPSWriteError.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 XPRS_NAMES_ROW 1 +#define XPRS_NAMES_COLUMN 2 #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. @@ -158,15 +144,36 @@ bool readParameter(XPRSprob const& prob, std::string const& name, return true; } +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 XPRSgetnumcols(const XPRSprob& mLp) { int nCols = 0; - XPRSgetintattrib(mLp, XPRS_ORIGINALCOLS, &nCols); + XPRSgetintattrib(mLp, XPRS_COLS, &nCols); return nCols; } int XPRSgetnumrows(const XPRSprob& mLp) { int nRows = 0; - XPRSgetintattrib(mLp, XPRS_ORIGINALROWS, &nRows); + XPRSgetintattrib(mLp, XPRS_ROWS, &nRows); return nRows; } @@ -183,10 +190,30 @@ int XPRSgetnodecnt(const XPRSprob& mLp) { } int XPRSsetobjoffset(const XPRSprob& mLp, double value) { - XPRSsetdblcontrol(mLp, XPRS_OBJRHS, value); + // TODO detect xpress version + static int indexes[1] = {-1}; + double values[1] = {-value}; + XPRSchgobj(mLp, 1, indexes, values); return 0; } -} // namespace + +void XPRSaddhint(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, @@ -201,19 +228,75 @@ 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 re-throw 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 RethrowCaughtExceptions() { + exceptions_mutex_.lock(); + for (const std::exception_ptr& ex : caught_exceptions_) { + try { + std::rethrow_exception(ex); + } catch (std::bad_exception const&) { + LOG(ERROR) << "Bad exception"; + } + } + 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 // index of an extracted variable is the column index in the XPRESS model. @@ -234,6 +317,9 @@ class XpressInterface : public MPSolverInterface { // Solve the problem using the parameter values specified. virtual MPSolver::ResultStatus Solve(MPSolverParameters const& param); + // Writes the model. + void Write(const std::string& filename) override; + // ----- Model modifications and extraction ----- // Resets extracted model virtual void Reset(); @@ -254,7 +340,7 @@ class XpressInterface : public MPSolverInterface { virtual void SetObjectiveCoefficient(MPVariable const* const variable, double coefficient); // Change the constant term in the linear objective. - virtual void SetObjectiveOffset(double value); + virtual void SetObjectiveOffset(double value) override; // Clear the objective from all its terms. virtual void ClearObjective(); @@ -274,9 +360,16 @@ class XpressInterface : public MPSolverInterface { // Query problem type. // Remember that problem type is a static property that is set // in the constructor and never changed. - bool IsContinuous() const override { return IsLP(); } - bool IsLP() const override { return !mMip; } - bool IsMIP() const override { return mMip; } + virtual bool IsContinuous() const { return IsLP(); } + virtual bool IsLP() const { return !mMip; } + virtual bool IsMIP() const { return mMip; } + + void SetStartingLpBasisInt( + const std::vector& variable_statuses, + const std::vector& constraint_statuses) override; + + void GetFinalLpBasisInt(std::vector& variable_statuses, + std::vector& constraint_statuses) override; void ExtractNewVariables() override; void ExtractNewConstraints() override; @@ -299,8 +392,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; @@ -325,10 +419,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; } + // 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(); // Transform XPRESS basis status to MPSolver basis status. static MPSolver::BasisStatus xformBasisStatus(int xpress_basis_status); @@ -370,109 +468,375 @@ class XpressInterface : public MPSolverInterface { // 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; + std::vector mutable mCstat; + std::vector mutable mRstat; + + std::vector mutable initCstat; + std::vector mutable initRstat; // Setup 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 XPRESS basis status to MPSolver basis status. +static MPSolver::BasisStatus xformBasisStatus(int xpress_basis_status); +// Transform MPSolver basis status to XPRESS status +static int convertToXpressBasisStatus( + MPSolver::BasisStatus mpsolver_basis_status); - const char* xpress_from_env = getenv("XPRESS"); - std::string xpresspath; +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; +} - 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; - } +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; +} - /** if not an OEM key */ - if (xpress_oem_license_key == 0) { - LOG(WARNING) << "XpressInterface : Initialising xpress-MP with parameter " - << xpresspath << std::endl; +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; +} - 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& getMapInt64Controls() { + static std::map mapControls = { + {"EXTRAELEMS", XPRS_EXTRAELEMS}, + {"EXTRASETELEMS", XPRS_EXTRASETELEMS}, + }; + return mapControls; } // Creates a LP/MIP instance. @@ -483,15 +847,17 @@ XpressInterface::XpressInterface(MPSolver* const solver, bool 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 = XPRSsetcbmessage(mLp, optimizermsg, (void*)this); 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)); } @@ -502,7 +868,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 ----- @@ -516,14 +896,15 @@ void XpressInterface::Reset() { status = XPRScreateprob(&mLp); CHECK_STATUS(status); DCHECK(mLp != nullptr); // should not be NULL if status=0 + int nReturn = XPRSsetcbmessage(mLp, optimizermsg, (void*)this); 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) { @@ -573,8 +954,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) { @@ -695,6 +1076,10 @@ 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(); } @@ -705,6 +1090,12 @@ void XpressInterface::AddVariable(MPVariable* const ct) { // 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. + + // 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(); } @@ -873,6 +1264,22 @@ MPSolver::BasisStatus XpressInterface::xformBasisStatus( } } +int convertToXpressBasisStatus(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; + 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) { @@ -881,17 +1288,16 @@ MPSolver::BasisStatus XpressInterface::row_status(int constraint_index) const { } if (CheckSolutionIsSynchronized()) { - if (!mRstat) { + if (mRstat.empty()) { int const rows = XPRSgetnumrows(mLp); - unique_ptr data(new int[rows]); - mRstat.swap(data); - CHECK_STATUS(XPRSgetbasis(mLp, 0, mRstat.get())); + mRstat.resize(rows); + CHECK_STATUS(XPRSgetbasis(mLp, mRstat.data(), 0)); } } else { - mRstat = 0; + mRstat.clear(); } - if (mRstat) { + if (!mRstat.empty()) { return xformBasisStatus(mRstat[constraint_index]); } else { LOG(FATAL) << "Row basis status not available"; @@ -907,17 +1313,16 @@ MPSolver::BasisStatus XpressInterface::column_status(int variable_index) const { } if (CheckSolutionIsSynchronized()) { - if (!mCstat) { + if (mCstat.empty()) { int const cols = XPRSgetnumcols(mLp); - unique_ptr data(new int[cols]); - mCstat.swap(data); - CHECK_STATUS(XPRSgetbasis(mLp, mCstat.get(), 0)); + mCstat.resize(cols); + CHECK_STATUS(XPRSgetbasis(mLp, 0, mCstat.data())); } } else { - mCstat = 0; + mCstat.clear(); } - if (mCstat) { + if (!mCstat.empty()) { return xformBasisStatus(mCstat[variable_index]); } else { LOG(FATAL) << "Column basis status not available"; @@ -951,7 +1356,7 @@ void XpressInterface::ExtractNewVariables() { 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]); + std::vector colnames; bool have_names = false; for (int j = 0, varidx = last_extracted; j < newcols; ++j, ++varidx) { @@ -959,8 +1364,10 @@ void XpressInterface::ExtractNewVariables() { 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(); + std::copy(var->name().begin(), var->name().end(), + std::back_inserter(colnames)); + colnames.push_back('\0'); + have_names = have_names || !var->name().empty(); obj[j] = solver_->objective_->GetCoefficient(var); } @@ -1047,6 +1454,10 @@ void XpressInterface::ExtractNewVariables() { CHECK_STATUS(XPRSaddcols(mLp, newcols, nonzeros, obj.get(), cmatbeg, cmatind.get(), cmatval.get(), lb.get(), ub.get())); + if (have_names) { + CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, colnames.data(), + 0, newcols - 1)); + } } } @@ -1064,6 +1475,10 @@ void XpressInterface::ExtractNewVariables() { CHECK_STATUS(XPRSaddcols(mLp, newcols, 0, obj.get(), cmatbeg.data(), cmatind.get(), cmatval.get(), lb.get(), ub.get())); + if (have_names) { + CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_COLUMN, colnames.data(), 0, + newcols - 1)); + } int const cols = XPRSgetnumcols(mLp); unique_ptr ind(new int[newcols]); for (int j = 0; j < cols; ++j) ind[j] = j; @@ -1137,8 +1552,11 @@ 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]); + std::vector name; unique_ptr rngval(new double[chunk]); + unique_ptr rngind(new int[chunk]); + bool haveRanges = false; + bool have_names = false; // Loop over the new constraints, collecting rows for up to // CHUNK constraints into the arrays so that adding constraints @@ -1147,7 +1565,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]; @@ -1162,6 +1579,7 @@ 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; @@ -1178,12 +1596,24 @@ void XpressInterface::ExtractNewConstraints() { } // Finally the name of the constraint. - name[nextRow] = ct->name().empty() ? 0 : ct->name().c_str(); + std::copy(ct->name().begin(), ct->name().end(), + std::back_inserter(name)); + name.push_back('\0'); + have_names = have_names || !ct->name().empty(); } 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 (have_names) { + CHECK_STATUS(XPRSaddnames(mLp, XPRS_NAMES_ROW, name.data(), offset, + offset + c - 1)); + } + if (haveRanges) { + CHECK_STATUS( + XPRSchgrhsrange(mLp, nextRow, rngind.get(), rngval.get())); + } } } } catch (...) { @@ -1207,7 +1637,7 @@ void XpressInterface::ExtractObjective() { // any non-zero duplicates. int const cols = XPRSgetnumcols(mLp); - DCHECK_EQ(last_variable_index_, cols); + // DCHECK_EQ(last_variable_index_, cols); unique_ptr ind(new int[cols]); unique_ptr val(new double[cols]); @@ -1233,6 +1663,7 @@ void XpressInterface::ExtractObjective() { void XpressInterface::SetParameters(const MPSolverParameters& param) { SetCommonParameters(param); + SetScalingMode(param.GetIntegerParam(MPSolverParameters::SCALING)); if (mMip) SetMIPParameters(param); } @@ -1314,6 +1745,39 @@ void XpressInterface::SetLpAlgorithm(int value) { } } +// Convert statuses for later use (Solve) +void XpressInterface::SetStartingLpBasisInt( + const std::vector& variable_statuses, + const std::vector& constraint_statuses) { + if (mMip) { + LOG(DFATAL) << __FUNCTION__ << " is only available for LP problems"; + return; + } + // Column = variable + initCstat = variable_statuses; + // Row = constraint + initRstat = constraint_statuses; +} + +void XpressInterface::GetFinalLpBasisInt( + std::vector& variable_statuses, + std::vector& constraint_statuses) { + if (mMip) { + LOG(DFATAL) << __FUNCTION__ << " is only available for LP problems"; + return; + } + + const int rows = XPRSgetnumrows(mLp); + const int cols = XPRSgetnumcols(mLp); + // 1. Resize vectors if needed + variable_statuses.resize(cols); + constraint_statuses.resize(rows); + + // 2. Extract basis + CHECK_STATUS( + XPRSgetbasis(mLp, constraint_statuses.data(), variable_statuses.data())); +} + bool XpressInterface::readParameters(std::istream& is, char sep) { // - parameters must be specified as NAME=VALUE // - settings must be separated by sep @@ -1370,21 +1834,12 @@ 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(); @@ -1407,13 +1862,15 @@ 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. solver_->SetSolverSpecificParametersAsString( solver_->solver_specific_parameter_string_); @@ -1426,10 +1883,29 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { -1.0 * solver_->time_limit_in_secs())); } - timer.Restart(); + // Load basis if present + // TODO : check number of variables / constraints + if (!mMip && !initCstat.empty() && !initRstat.empty()) { + CHECK_STATUS(XPRSloadbasis(mLp, initRstat.data(), initCstat.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)); + } + // Solve. - // Do not CHECK_STATUS here since some errors still allow us to query useful - // information. + // Do not CHECK_STATUS here since some errors (for example CPXERR_NO_MEMORY) + // still allow us to query useful information. + timer.Restart(); + int xpressstat = 0; if (mMip) { if (this->maximize_) @@ -1445,8 +1921,18 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { XPRSgetintattrib(mLp, XPRS_LPSTATUS, &xpressstat); } + if (mp_callback_wrapper != nullptr) { + mp_callback_wrapper->RethrowCaughtExceptions(); + delete mp_callback_wrapper; + } + + if (!(mMip ? (xpressstat == XPRS_MIP_OPTIMAL) + : (xpressstat == 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); @@ -1459,9 +1945,9 @@ MPSolver::ResultStatus XpressInterface::Solve(MPSolverParameters const& param) { VLOG(1) << absl::StrFormat("XPRESS solution status %d.", xpressstat); // 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 ? (xpressstat == XPRS_MIP_OPTIMAL || + xpressstat == XPRS_MIP_SOLUTION) + : (!mMip && xpressstat == XPRS_LP_OPTIMAL)); // Get problem dimensions for solution queries below. int const rows = XPRSgetnumrows(mLp); @@ -1498,15 +1984,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) { @@ -1593,8 +2079,213 @@ 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) { + throw MPSWriteError("Failed to write MPS."); + } +} + MPSolverInterface* BuildXpressInterface(bool mip, MPSolver* const solver) { return new XpressInterface(solver, mip); } +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(targetMap, setter, converter) \ + { \ + auto matchingParamIter = targetMap.find(paramAndValuePair.first); \ + if (matchingParamIter != targetMap.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) +{ + operations_research::XpressInterface * 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(NULL); + 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 colind(new int[len]); + unique_ptr val(new double[len]); + + for (std::size_t i = 0; i < len ; ++i) { + colind[i] = solver_->solution_hint_[i].first->index(); + val[i] = solver_->solution_hint_[i].second; + } + XPRSaddhint(mLp, len, val.get(), colind.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, XPRSgetnodecnt(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 = XPRSgetnumcols(*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; + } + XPRSaddhint(*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 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..ec7373c7dd --- /dev/null +++ b/ortools/xpress/CMakeLists.txt @@ -0,0 +1,25 @@ +file(GLOB _SRCS "*.h" "*.cc") +set(NAME ${PROJECT_NAME}_xpress) + +# Will be merge in libortools.so +#add_library(${NAME} STATIC ${_SRCS}) +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 + $<$:Coin::Cbc>) +#add_library(${PROJECT_NAME}::xpress ALIAS ${NAME}) diff --git a/ortools/xpress/environment.cc b/ortools/xpress/environment.cc new file mode 100644 index 0000000000..a7ddf31a9e --- /dev/null +++ b/ortools/xpress/environment.cc @@ -0,0 +1,366 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/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 XPRSsetcbmessage = nullptr; +std::function XPRSminim = nullptr; +std::function XPRSmaxim = nullptr; + +absl::Status 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(&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(&XPRSsetcbmessage, "XPRSsetcbmessage"); + xpress_dynamic_library->GetFunction(&XPRSminim, "XPRSminim"); + xpress_dynamic_library->GetFunction(&XPRSmaxim, "XPRSmaxim"); + + auto notFound = xpress_dynamic_library->FunctionsNotFound(); + if (!notFound.empty()) { + return absl::NotFoundError(absl::StrCat("Could not find the following functions (list may not be exhaustive). [", + absl::StrJoin(notFound, "', '"), + "]. Please make sure that your XPRESS install is up-to-date (>= 8.13.0).")); + } + return absl::OkStatus(); +} + +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* xpress_home_from_env = getenv("XPRESSDIR"); + if (xpress_home_from_env != nullptr) { +#if defined(_MSC_VER) // Windows + potential_paths.push_back( + absl::StrCat(xpress_home_from_env, "\\bin\\xprs.dll")); +#elif defined(__APPLE__) // OS X + potential_paths.push_back( + absl::StrCat(xpress_home_from_env, "/lib/libxprs.dylib")); +#elif defined(__GNUC__) // Linux + potential_paths.push_back( + absl::StrCat(xpress_home_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.\n"; + } + + // 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()) { + xpress_load_status = LoadXpressFunctions(&xpress_library); + } 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(ERROR) << "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..2c2a52c406 --- /dev/null +++ b/ortools/xpress/environment.h @@ -0,0 +1,492 @@ +// Copyright 2010-2021 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OR_TOOLS_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 + +#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 XPRSsetcbmessage; +extern std::function XPRSminim; +extern std::function XPRSmaxim; + +} // namespace operations_research + +#endif // OR_TOOLS_XPRESS_ENVIRONMENT_H diff --git a/ortools/xpress/parse_header_xpress.py b/ortools/xpress/parse_header_xpress.py new file mode 100644 index 0000000000..67898761d8 --- /dev/null +++ b/ortools/xpress/parse_header_xpress.py @@ -0,0 +1,330 @@ +#!/usr/bin/env python3 +"""Xpress header parser script to generate code for the environment.{cc|h}. + +To use, run the script + ./parse_header_xpress.py + +This will printout on the console 9 sections: + +------------------- header ------------------- + +to copy paste in environment.h + +------------------- define ------------------- + +to copy in the define part of environment.cc + +------------------- assign ------------------- + +to copy in the assign part of environment.cc + +------------------- string parameters ------------------- + +to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc + +------------------- string parameters tests ------------------- + +to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- double parameters ------------------- + +to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc + +------------------- double parameters tests ------------------- + +to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- int parameters ------------------- + +to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc + +------------------- int parameters tests ------------------- + +to copy in the "setIntControl" TEST of linear_solver/unittests/xpress_interface.cc + +------------------- int64 parameters ------------------- + +to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc + +------------------- int64 parameters tests ------------------- + +to copy in the "setInt64Control" TEST of linear_solver/unittests/xpress_interface.cc +""" + +import argparse +import re +from enum import Enum + + +# from absl import app + +# This enum is used to detect different sections in the xprs.h document +class XprsDocumentSection(Enum): + STRING_PARAMS = 1 + DOUBLE_PARAMS = 2 + INT_PARAMS = 3 + INT64_PARAMS = 4 + OTHER = 5 + + +class XpressHeaderParser(object): + """Converts xprs.h to something pastable in ./environment.h|.cc.""" + + def __init__(self): + self.__header = '' + self.__define = '' + self.__assign = '' + self.__state = 0 + self.__return_type = '' + self.__args = '' + self.__fun_name = '' + self.__string_parameters = '' + self.__string_parameters_unittest = '' + self.__double_parameters = '' + self.__double_parameters_unittest = '' + self.__int_parameters = '' + self.__int_parameters_unittest = '' + self.__int64_parameters = '' + self.__int64_parameters_unittest = '' + # These are the definitions required for compiling the XPRESS interface, excluding control parameters + self.__required_defines = {"XPRS_STOP_USER", "XPRS_TYPE_NOTDEFINED", "XPRS_TYPE_INT", "XPRS_TYPE_INT64", + "XPRS_TYPE_DOUBLE", "XPRS_PLUSINFINITY", "XPRS_MINUSINFINITY", "XPRS_MAXBANNERLENGTH", "XPVERSION", + "XPRS_LPOBJVAL", "XPRS_MIPOBJVAL", "XPRS_BESTBOUND", "XPRS_OBJRHS", "XPRS_OBJSENSE", + "XPRS_ROWS", "XPRS_SIMPLEXITER", "XPRS_LPSTATUS", "XPRS_MIPSTATUS", "XPRS_NODES", + "XPRS_COLS", "XPRS_LP_OPTIMAL", "XPRS_LP_INFEAS", "XPRS_LP_UNBOUNDED", + "XPRS_MIP_SOLUTION", "XPRS_MIP_INFEAS", "XPRS_MIP_OPTIMAL", "XPRS_MIP_UNBOUNDED", + "XPRS_OBJ_MINIMIZE", "XPRS_OBJ_MAXIMIZE"} + self.__missing_required_defines = self.__required_defines + # These enum will detect control parameters that will all be imported + self.__doc_section = XprsDocumentSection.OTHER + # These parameters are not supported + self.__excluded_defines = {"XPRS_COMPUTE"} + # These are the functions required for compiling the XPRESS interface + self.__required_functions = {"XPRScreateprob", "XPRSdestroyprob", "XPRSinit", "XPRSfree", "XPRSgetlicerrmsg", + "XPRSlicense", "XPRSgetbanner", "XPRSgetversion", "XPRSsetdefaultcontrol", + "XPRSsetintcontrol", "XPRSsetintcontrol64", "XPRSsetdblcontrol", + "XPRSsetstrcontrol", "XPRSgetintcontrol", "XPRSgetintcontrol64", + "XPRSgetdblcontrol", "XPRSgetstringcontrol", "XPRSgetintattrib", + "XPRSgetdblattrib", "XPRSloadlp", "XPRSloadlp64", "XPRSgetobj", "XPRSgetrhs", + "XPRSgetrhsrange", "XPRSgetlb", "XPRSgetub", "XPRSgetcoef", "XPRSaddrows", + "XPRSdelrows", "XPRSaddcols", "XPRSaddnames", "XPRSgetnames", "XPRSdelcols", "XPRSchgcoltype", "XPRSloadbasis", + "XPRSpostsolve", "XPRSchgobjsense", "XPRSgetlasterror", "XPRSgetbasis", + "XPRSwriteprob", "XPRSgetrowtype", "XPRSgetcoltype", "XPRSgetlpsol", + "XPRSgetmipsol", "XPRSchgbounds", "XPRSchgobj", "XPRSchgcoef", "XPRSchgmcoef", + "XPRSchgrhs", "XPRSchgrhsrange", "XPRSchgrowtype", "XPRSaddcbmessage", "XPRSsetcbmessage", + "XPRSminim", "XPRSmaxim", "XPRSaddmipsol", "XPRSaddcbintsol", "XPRSremovecbintsol", + "XPRSinterrupt"} + self.__missing_required_functions = self.__required_functions + self.__XPRSprob_section = False + + def write_define(self, symbol, value): + if symbol in self.__excluded_defines: + print('skipping ' + symbol) + return + + # If it is a control parameter, import it to expose it to the user + # Else import it only if required + if self.__doc_section in [XprsDocumentSection.STRING_PARAMS, XprsDocumentSection.DOUBLE_PARAMS, + XprsDocumentSection.INT_PARAMS, XprsDocumentSection.INT64_PARAMS]: + self.__header += f'#define {symbol} {value}\n' + ortools_symbol = symbol.replace("XPRS_", "") + if self.__doc_section == XprsDocumentSection.STRING_PARAMS: + self.__string_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__string_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, "default_value"}},\n' + elif self.__doc_section == XprsDocumentSection.DOUBLE_PARAMS: + self.__double_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__double_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1.}},\n' + elif self.__doc_section == XprsDocumentSection.INT_PARAMS: + self.__int_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__int_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n' + elif self.__doc_section == XprsDocumentSection.INT64_PARAMS: + self.__int64_parameters += f'{{\"{ortools_symbol}\", {symbol}}},\n' + self.__int64_parameters_unittest += f'{{\"{ortools_symbol}\", {symbol}, 1}},\n' + elif symbol in self.__required_defines: + self.__header += f'#define {symbol} {value}\n' + self.__missing_required_defines.remove(symbol) + else: + print('skipping ' + symbol) + + def write_fun(self, return_type, name, args): + if name in self.__required_functions: + self.__header += f'extern std::function<{return_type}({args})> {name};\n' + self.__define += f'std::function<{return_type}({args})> {name} = nullptr;\n' + self.__assign += f' xpress_dynamic_library->GetFunction(&{name}, ' + self.__assign += f'"{name}");\n' + self.__missing_required_functions.remove(name) + else: + print('skipping ' + name) + + def parse(self, filepath): + """Main method to parser the Xpress header.""" + + with open(filepath) as fp: + all_lines = fp.read() + + self.__XPRSprob_section = False + + for line in all_lines.splitlines(): + if not line: # Ignore empty lines. + continue + + self.detect_XPRSprob_section(line) + + if re.match(r'/\*', line, re.M): # Comments in xprs.h indicate the section + if self.__XPRSprob_section: + if "string control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.STRING_PARAMS + elif "double control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.DOUBLE_PARAMS + elif "integer control parameters" in line.lower() and "64-bit" in line.lower(): + self.__doc_section = XprsDocumentSection.INT64_PARAMS + elif "integer control parameters" in line.lower(): + self.__doc_section = XprsDocumentSection.INT_PARAMS + else: + self.__doc_section = XprsDocumentSection.OTHER + else: + self.__doc_section = XprsDocumentSection.OTHER + + if self.__state == 0: + match_def = re.match(r'#define ([A-Z0-9_]*)\s+([^/]+)', line, + re.M) + if match_def: + self.write_define(match_def.group(1), match_def.group(2)) + continue + + # Single line function definition. + match_fun = re.match( + r'([a-z]+) XPRS_CC (XPRS[A-Za-z0-9_]*)\(([^;]*)\);', line, + re.M) + if match_fun: + self.write_fun(match_fun.group(1), match_fun.group(2), + match_fun.group(3)) + continue + + # Simple type declaration (i.e. int XPRS_CC). + match_fun = re.match(r'([a-z]+) XPRS_CC\s*$', line, re.M) + if match_fun: + self.__return_type = match_fun.group(1) + self.__state = 1 + continue + + # Complex type declaration with pointer. + match_fun = re.match(r'([A-Za-z0-9 ]+)\*\s*XPRS_CC\s*$', line, + re.M) + if match_fun: + self.__return_type = match_fun.group(1) + '*' + self.__state = 1 + continue + + elif self.__state == 1: # The return type was defined at the line before. + # Function definition terminates in this line. + match_fun = re.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)\);', line, + re.M) + if match_fun: + self.write_fun(match_fun.group(1), self.__return_type, + match_fun.group(2)) + self.__state = 0 + self.__return_type = '' + continue + + # Function definition does not terminate in this line. + match_fun = re.match(r'\s*(XPRS[A-Za-z0-9_]*)\(([^;]+)$', line, + re.M) + if match_fun: + self.__fun_name = match_fun.group(1) + self.__args = match_fun.group(2) + self.__state = 2 + continue + + elif self.__state == 2: # Extra arguments. + # Arguments end in this line. + match_fun = re.match(r'\s*([^;]+)\);', line, re.M) + if match_fun: + self.__args += match_fun.group(1) + self.write_fun(self.__fun_name, self.__return_type, + self.__args) + self.__args = '' + self.__fun_name = '' + self.__return_type = '' + self.__state = 0 + continue + + # Arguments do not end in this line. + match_fun = re.match(r'\s*([^;]+)$', line, re.M) + if match_fun: + self.__args += match_fun.group(1) + continue + + def detect_XPRSprob_section(self, line): + """This method detects the section between these commented lines: + /***************************************************************************\ + * control parameters for XPRSprob * + ... + /***************************************************************************\ + """ + if " * control parameters for XPRSprob" in line: + self.__XPRSprob_section = True + elif self.__XPRSprob_section and \ + "/***************************************************************************\\" in line: + self.__XPRSprob_section = False + + def output(self): + """Output the 3 generated code on standard out.""" + print('------------------- header (to copy in environment.h) -------------------') + print(self.__header) + + print('------------------- define (to copy in the define part of environment.cc) -------------------') + print(self.__define) + + print('------------------- assign (to copy in the assign part of environment.cc) -------------------') + print(self.__assign) + + print('------------------- string params (to copy in the "getMapStringControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__string_parameters) + + print('------------------- string params test (to copy in the "setStringControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__string_parameters_unittest) + + print('------------------- double params (to copy in the "getMapDoubleControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__double_parameters) + + print('------------------- double params test (to copy in the "setDoubleControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__double_parameters_unittest) + + print('------------------- int params (to copy in the "getMapIntControls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__int_parameters) + + print('------------------- int params test (to copy in the "setIntControls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__int_parameters_unittest) + + print('------------------- int64 params (to copy in the "getMapInt64Controls" function of linear_solver/xpress_interface.cc) -------------------') + print(self.__int64_parameters) + + print('------------------- int64 params test (to copy in the "setInt64Controls" TEST of linear_solver/unittests/xpress_interface.cc) -------------------') + print(self.__int64_parameters_unittest) + + def print_missing_elements(self): + if self.__missing_required_defines: + print('------WARNING------ missing required defines -------------------') + print(self.__missing_required_defines) + + if self.__missing_required_functions: + print('------WARNING------ missing required functions -------------------') + print(self.__missing_required_functions) + + if self.__missing_required_defines or self.__missing_required_functions: + raise LookupError("Some required defines or functions are missing (see detail above)") + + +def main(path: str) -> None: + parser = XpressHeaderParser() + parser.parse(path) + parser.output() + parser.print_missing_elements() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Xpress header parser.') + parser.add_argument('filepath', type=str) + args = parser.parse_args() + main(args.filepath)