# --------------------------------------------------------------------------
# Source file provided under Apache License, Version 2.0, January 2004,
# http://www.apache.org/licenses/
# (c) Copyright IBM Corp. 2015, 2022
# --------------------------------------------------------------------------
'''Provides utility functions about the runtime environment.
You can display information about your runtime environment using::
$ python
>>> from docplex.mp.environment import Environment
>>> Environment().print_information()
or by invoking the `docplex.mp.environment` package on your shell command line::
$ python -m docplex.mp.environment
* system is: Linux 64bit
* Python version 3.6.1, located at: /usr/bin/python
* docplex is present, version is (2, 9, 0)
* CPLEX library is present, version is 12.9.0.0, located at: /usr/local/CPLEX_Studio129/cplex/python/3.6/x86-64_linux
'''
try:
import importlib.util as importlib_util
except ImportError: # pragma: no cover
importlib_util = None # Python 2
import platform
import os
import sys
import warnings
from docplex.mp.error_handler import docplex_fatal
min_cplex_major = 12
min_cplex_minor = 8
def env_is_64_bit():
return sys.maxsize > 2 ** 32
# maps paths to modules for already loaded cplexes
# for path == None, key is "__NONE__"
_loaded_cplexes = {}
# noinspection PyPep8
[docs]class Environment(object):
""" This class detects and contains information regarding other modules of interest, such as
whether CPLEX, `numpy`, and `matplotlib` are installed.
"""
_default_env = None # The default env singleton
""" This class detects and contains information regarding other modules of interest, such as
whether CPLEX, `numpy`, and `matplotlib` are installed.
"""
def __init__(self, start_auto_configure=True, logger=None):
"""
__init__(self)
"""
self._found_cplex = False
self._cplex_version = ''
self._cplex_location = None
self._found_numpy = None
self._numpy_version = None
self._numpy_hook = None
self._found_pandas = None
self._pandas_version = ''
self._found_matplotlib = None
self._matplotlib_version = None
self._python_version = platform.python_version()
self._system = platform.system()
self._machine = platform.machine()
self._bitness = platform.architecture()[0]
self._is64bit = sys.maxsize > 2 ** 32
if start_auto_configure:
self.auto_configure(logger=logger)
# class variable
# Must be true if python versin is > 3.6
env_is_python36 = (sys.version_info.major >= 3) and (sys.version_info.minor >= 6)
def _get_numpy_hook(self):
return self._numpy_hook
def _set_numpy_hook(self, hook):
self._numpy_hook = hook
if hook is not None:
if self.has_numpy: # now that we have set a hook, do check for numpy
hook() # if numpy is present, do call the hook
numpy_hook = property(_get_numpy_hook, _set_numpy_hook)
def equals(self, other):
if type(other) != Environment:
return False
if self.has_cplex != other.has_cplex:
return False
if self.cplex_version != other.cplex_version:
return False
if self.has_numpy != other.has_numpy:
return False
if self.has_matplotlib != other.has_matplotlib:
return False
if self.has_pandas != other.has_pandas:
return False
return True
@property
def has_cplex(self):
"""True if the CPLEX libraries are available.
The cplex libraries search order is:
- import the module `import cplex` if the import is sucessful
- import the module in location $CPLEX_STUDIO_DIR1210/cplex/python/<python.version>/<platform>
- import the module in location $CPLEX_STUDIO_DIR129/cplex/python/<python.version>/<platform>
- import the module in location $CPLEX_STUDIO_DIR128/cplex/python/<python.version>/<platform>
"""
return self._found_cplex
def hash_cplex_with_version_min(self, min_version):
return self.has_cplex and self._cplex_version >= min_version
def check_cplex_version(self):
cpx_version = self.cplex_version_as_tuple
if self.has_cplex and cpx_version < (min_cplex_major, min_cplex_minor):
s_min_version = "{0}.{1}".format(min_cplex_major, min_cplex_minor)
docplex_fatal("DOcplex supports Cplex from {0} up, unsupported version {1} was found"
.format(s_min_version, self.cplex_version))
@property
def cplex_lp_obj_kw(self):
if self.has_cplex and self.cplex_version_as_tuple >= (12, 10):
obj_kw = "obj1"
else:
obj_kw = "obj"
return obj_kw
@staticmethod
def cplex_platform():
sys_platform = platform.system()
machine = platform.machine()
if sys_platform == 'Windows':
return 'x64_win64'
elif sys_platform == 'Darwin':
if machine == 'x86_64':
return 'x86-64_osx'
else:
return 'arm64_osx'
elif sys_platform == 'Linux':
if machine == 'x86_64':
return 'x86-64_linux'
else:
# different flavors of linux (ppc64le_linux, s390x_linux)
return machine + "_linux"
return None
@staticmethod
def cplex_distribname():
distribname = Environment.cplex_platform()
if distribname == 'x64_win64':
distribname = 'x64_windows'
return distribname
@property
def cplex_version(self):
return self._cplex_version
@property
def cplex_version_as_tuple(self):
cpxv = self._cplex_version
return self.parse_cplex_version(cpxv, fallback=None)
@property
def has_matplotlib(self):
"""True if the `matplotlib` libraries are available.
"""
if self._found_matplotlib is None:
self.check_matplotlib()
return self._found_matplotlib
@property
def has_pandas(self):
"""True if the `pandas` libraries are available.
"""
self.check_pandas()
return self._found_pandas
@property
def pandas_version(self):
self.check_pandas()
return self._pandas_version
@property
def cplex_location(self):
"""The system path where CPLEX is located, if present. Otherwise, returns None.
"""
return self._cplex_location
@property
def has_numpy(self):
"""True if the `numpy` libraries are available.
"""
self.check_numpy()
return self._found_numpy
[docs] def is_64bit(self):
"""True if running on a 64-bit platform.
"""
return self._is64bit
@property
def python_version(self):
""" Returns the Python version as a string"""
return platform.python_version()
def auto_configure(self, logger=None):
self.check_cplex(logger=logger)
# check for pandas (docplex_wml studio)
self.check_pandas()
def check_all(self):
self.check_cplex()
self.check_pandas()
self.check_numpy()
self.check_matplotlib()
[docs] def get_cplex_module(self, default_location=None, logger=None):
'''Returns the cplex module.
If `default_location` is None, this method will try to import the cplex module in the following order:
- by importing the module `import cplex` if the import is sucessful
- by importing the module in location $CPLEX_STUDIO_DIR20101/cplex/python
- by importing the module in location $CPLEX_STUDIO_DIR201/cplex/python
- by importing the module in location $CPLEX_STUDIO_DIR1210/cplex/python
- by importing the module in location $CPLEX_STUDIO_DIR129/cplex/python
- by importing the module in location $CPLEX_STUDIO_DIR128/cplex/python
If `default_location` is a valid path and contains a valid python package,
`cplex` is imported from the specified location.
If `default_location` is a valid path and
`<default_location>/cplex/python/<python_version>/<platform>` exists, `cplex`
is imported from that location
If `cplex` could not be found, this method returns `None`
'''
cplex = None
def load_cplex(location, version=""):
# we cache loaded modules as we want to make sure that modules are loaded
# only once (same behaviour than 'import')
# example location: C:\Program Files\IBM\ILOG\CPLEX_Studio1210\cplex\python\3.7\x64_win64\
cplex = _loaded_cplexes.get(location, None)
if cplex is None:
absolute_name = "cplex%s" % version
module_location = os.path.join(location, "cplex", "__init__.py")
if not os.path.isfile(module_location):
return None
if importlib_util:
spec = importlib_util.spec_from_file_location(absolute_name,
module_location)
cplex = importlib_util.module_from_spec(spec)
# TODO: Error out if one was already loaded
previous = sys.modules.get(absolute_name, None)
if previous is not None:
lo = location if location else "default sys.path"
raise RuntimeError("Cannot load cplex from %s, a previous version has already been loaded from %s" % (lo, previous.__file__))
sys.modules[absolute_name] = cplex
spec.loader.exec_module(cplex)
else:
import imp
cplex = imp.load_source(absolute_name.split('.')[-1], module_location)
_loaded_cplexes[location] = cplex
return cplex
def load_cplex_from_cos_root(cos_root, version=""):
platform = Environment.cplex_platform()
if platform is None:
raise UnsupportedPlatformError("Platform not supported, please install cplex python module")
python_version = '%s.%s' % (sys.version_info[0],
sys.version_info[1])
full_path = os.path.join(cos_root, 'cplex', 'python', python_version, platform)
return load_cplex(full_path, version=version)
if default_location is None:
try:
import cplex # @UnresolvedImport
if logger is not None:
logger.info("Found cplex with 'import cplex'")
except (ImportError, ModuleNotFoundError):
# in py3.7, ModuleNotFoundError is raised
user_cos_location = os.environ.get('DOCPLEX_COS_LOCATION', None)
if user_cos_location is not None:
cplex = load_cplex_from_cos_root(user_cos_location)
if cplex is None:
# user provided a cos location that was not right, raise warning
warnings.warn("Could not load CPLEX from Location provided by DOCPLEX_COS_LOCATION=%s. Using default locations." % user_cos_location)
if cplex is None:
try_environs = ['CPLEX_STUDIO_DIR2211',
'CPLEX_STUDIO_DIR221',
'CPLEX_STUDIO_DIR20101',
'CPLEX_STUDIO_DIR201',
'CPLEX_STUDIO_DIR1210',
'CPLEX_STUDIO_DIR129',
'CPLEX_STUDIO_DIR128']
for t in try_environs:
loc = os.environ.get(t, None)
# version = t[len('CPLEX_STUDIO_DIR'):] if loc else ""
# currently, there are some import in CPLEX, like:
# File "C:\Program Files\IBM\ILOG\CPLEX_Studio1210\cplex\python\3.7\x64_win64\cplex\_internal\_pycplex_platform.py", line 24, in <module>
# from cplex._internal.py37_cplex12100 import *
# that prevent us from loading multiples instances of cplex
# so for now, let's just ignore this version
cplex = load_cplex_from_cos_root(loc) if loc else None
if logger is not None:
logger.info("Looking into location %s, found = %s" % (loc, (cplex is not None)))
if cplex is not None:
return cplex
else:
if os.path.isfile(os.path.join(default_location, "__init__.py")):
cplex = load_cplex(default_location)
else:
cplex = load_cplex_from_cos_root(default_location)
return cplex
def check_cplex(self, logger=None):
# detecting CPLEX using default search location
cplex = self.get_cplex_module(logger=logger)
self._found_cplex = (cplex is not None)
if self.has_cplex:
cplex_module_file = cplex.__file__
if cplex_module_file:
self._cplex_location = os.path.dirname(os.path.dirname(cplex_module_file))
try:
self._cplex_version = cplex.__version__
except AttributeError:
# older version: use an instance
cpx = cplex.Cplex()
# format: MM.mm.rr.ff e.g.e 12.6.2.0
self._cplex_version = cpx.get_version()
# terminate the dummy instance...
del cpx
def check_numpy(self):
if self._found_numpy is None:
try:
import numpy.version as npv
self._found_numpy = True
self._numpy_version = npv.version
self_numpy_hook = self._numpy_hook
if self_numpy_hook is not None:
# lazy call the hook once at first check time.
self_numpy_hook()
except ImportError: # pragma: no cover
self._found_numpy = False
self._numpy_version = None
return self._found_numpy
def check_matplotlib(self):
try:
from matplotlib import __version__ as matplotlib_version
self._found_matplotlib = True
self._matplotlib_version = matplotlib_version
except ImportError: # pragma: no cover
self._found_matplotlib = False
def check_pandas(self):
if self._found_pandas is None:
try:
import pandas
self._found_pandas = True
self._pandas_version = pandas.__version__
except ImportError: # pragma: no cover
self._found_pandas = False
@staticmethod
def _display_feature(is_present, feature_name, feature_version, location=None):
safe_feature_version = feature_version or "?"
if is_present is None:
pass # we dont know yet
elif is_present:
if location:
print("* {0} is present, version is {1}, located at: {2}".format(feature_name, safe_feature_version,
location))
else:
print("* {0} is present, version is {1}".format(feature_name, safe_feature_version))
else:
print("* {0} is not available".format(feature_name))
@property
def max_nb_digits(self):
# source: https://en.wikipedia.org/wiki/IEEE_floating_point
return 17 if self.is_64bit() else 9
@property
def bitness(self):
return 64 if self.is_64bit() else 32
def print_information(self):
print("* system is: {0} {1}".format(self._system, self._bitness))
from sys import version_info
from docplex.mp import __version_info__
python_version = '%s.%s.%s' % (version_info[0], version_info[1], version_info[2])
print("* Python version %s, located at: %s" % (python_version, sys.executable))
self._display_feature(True, "docplex", "%d.%d.%d" % __version_info__)
self._display_feature(self.has_cplex, "CPLEX library", self._cplex_version, self._cplex_location)
self._display_feature(self._found_pandas, "pandas", self._pandas_version)
self._display_feature(self._found_numpy, "numpy", self._numpy_version)
self._display_feature(self._found_matplotlib, "matplotlib", self._matplotlib_version)
@staticmethod
def closed_env():
closed = Environment(start_auto_configure=False)
# force matplotlib absent
closed._found_matplotlib = False
closed._found_numpy = False
closed._found_pandas = False
return closed
@staticmethod
def make_new_configured_env():
# returns a fresh new environment
return Environment(start_auto_configure=True)
@staticmethod
def get_default_env():
if not Environment._default_env:
Environment._default_env = Environment.make_new_configured_env()
return Environment._default_env
@staticmethod
def parse_cplex_version(version_text, fallback=None):
# parse such strings as: 20.1.0.0 | 2020-11-10 | 9bedb6d68
if not version_text:
return fallback
barpos = version_text.find('|')
if barpos > 0:
assert barpos >= 2
before_bar = barpos-1
baseline_version = version_text[:before_bar]
else:
baseline_version = version_text
return tuple(float(x) for x in baseline_version.split('.'))
# for pickling: recreate a fresh environment at the other end of pickle.
def __reduce__(self):
return Environment.make_new_configured_env, ()
def get_closed_environment():
# This instance assumes nothing is found, CPLEX, numpy, etc, to be used for tests
return Environment.closed_env()
if __name__ == '__main__':
Environment().print_information()