diff --git a/qiskit_algorithms/utils/__init__.py b/qiskit_algorithms/utils/__init__.py index 3aa70917..f4461e5b 100644 --- a/qiskit_algorithms/utils/__init__.py +++ b/qiskit_algorithms/utils/__init__.py @@ -27,6 +27,7 @@ is_main_process apply_prefix detach_prefix + wrap_method Algorithm Utilities =================== @@ -55,6 +56,11 @@ are run on a device or simulator by passing a QuantumInstance setup with the desired backend etc. + +Optional Depedency Checkers (:mod:`qiskit.utils.optionals`) +=========================================================== + +.. automodule:: qiskit.utils.optionals """ from .quantum_instance import QuantumInstance @@ -63,6 +69,10 @@ from .multiprocessing import local_hardware_info from .multiprocessing import is_main_process from .units import apply_prefix, detach_prefix +from .classtools import wrap_method +from .lazy_tester import LazyDependencyManager, LazyImportTester, LazySubprocessTester + +from . import optionals from .circuit_utils import summarize_circuits from .entangler_map import get_entangler_map, validate_entangler_map @@ -72,6 +82,9 @@ __all__ = [ + "LazyDependencyManager", + "LazyImportTester", + "LazySubprocessTester", "QuantumInstance", "summarize_circuits", "get_entangler_map", diff --git a/qiskit_algorithms/utils/classtools.py b/qiskit_algorithms/utils/classtools.py new file mode 100644 index 00000000..71c17754 --- /dev/null +++ b/qiskit_algorithms/utils/classtools.py @@ -0,0 +1,161 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tools useful for creating decorators, and other high-level callables.""" + +import functools +import inspect +import types +from typing import Type, Callable + + +# On user-defined classes, `__new__` is magically inferred to be a staticmethod, `__init_subclass__` +# is magically inferred to be a class method and `__prepare__` must be defined as a classmethod, but +# the CPython types implemented in C (such as `object` and `type`) are `types.BuiltinMethodType`, +# which we can't distinguish properly, so we need a little magic. +_MAGIC_STATICMETHODS = {"__new__"} +_MAGIC_CLASSMETHODS = {"__init_subclass__", "__prepare__"} + +# `type` itself has several methods (mostly dunders). When we are wrapping those names, we need to +# make sure that we don't interfere with `type.__getattribute__`'s handling that circumvents the +# normal inheritance rules when appropriate. +_TYPE_METHODS = set(dir(type)) + + +class _lift_to_method: # pylint: disable=invalid-name + """A decorator that ensures that an input callable object implements ``__get__``. It is + returned unchanged if so, otherwise it is turned into the default implementation for functions, + which makes them bindable to instances. + + Python-space functions and lambdas already have this behaviour, but builtins like ``print`` + don't; using this class allows us to do:: + + wrap_method(MyClass, "maybe_mutates_arguments", before=print, after=print) + + to simply print all the arguments on entry and exit of the function, which otherwise wouldn't be + valid, since ``print`` isn't a descriptor. + """ + + __slots__ = ("_method",) + + def __new__(cls, method): + if hasattr(method, "__get__"): + return method + return super().__new__(cls) + + def __init__(self, method): + if method is self: + # Prevent double-initialisation if we are passed an instance of this object to lift. + return + self._method = method + + def __get__(self, obj, objtype): + # This is effectively the same implementation as `types.FunctionType.__get__`, but we can't + # bind that directly because it also includes a requirement that its `self` reference is of + # the correct type, and this isn't. + if obj is None: + return self._method + return types.MethodType(self._method, obj) + + +class _WrappedMethod: + """Descriptor which calls its two arguments in succession, correctly handling instance- and + class-method calls. + + It is intended that this class will replace the attribute that ``inner`` previously was on a + class or instance. When accessed as that attribute, this descriptor will behave it is the same + function call, but with the ``function`` called before or after. + """ + + __slots__ = ("_method_decorator", "_method_has_get", "_method", "_before", "_after") + + def __init__(self, method, before=None, after=None): + if isinstance(method, (classmethod, staticmethod)): + self._method_decorator = type(method) + elif isinstance(method, type(self)): + self._method_decorator = method._method_decorator + elif getattr(method, "__name__", None) in _MAGIC_STATICMETHODS: + self._method_decorator = staticmethod + elif getattr(method, "__name__", None) in _MAGIC_CLASSMETHODS: + self._method_decorator = classmethod + else: + self._method_decorator = _lift_to_method + before = (self._method_decorator(before),) if before is not None else () + after = (self._method_decorator(after),) if after is not None else () + if isinstance(method, type(self)): + self._method = method._method + self._before = before + method._before + self._after = method._after + after + else: + self._before = before + self._after = after + self._method = method + # If the inner method doesn't have `__get__` (like some builtin methods), it's faster to + # test a Boolean each time than the repeatedly raise and catch an exception, which is what + # `hasattr` does. + self._method_has_get = hasattr(self._method, "__get__") + + def __get__(self, obj, objtype=None): + # `self._method` doesn't invoke the `_method` descriptor (if it is one) because that only + # happens for class variables. Here it's an instance variable, so we can pass through `obj` + # and `objtype` correctly like this. + method = self._method.__get__(obj, objtype) if self._method_has_get else self._method + + @functools.wraps(method) + def out(*args, **kwargs): + for callback in self._before: + callback.__get__(obj, objtype)(*args, **kwargs) + retval = method(*args, **kwargs) + for callback in self._after: + callback.__get__(obj, objtype)(*args, **kwargs) + return retval + + return out + + +def wrap_method(cls: Type, name: str, *, before: Callable = None, after: Callable = None): + """Wrap the functionality the instance- or class method ``cls.name`` with additional behaviour + ``before`` and ``after``. + + This mutates ``cls``, replacing the attribute ``name`` with the new functionality. This is + useful when creating class decorators. The method is allowed to be defined on any parent class + instead. + + If either ``before`` or ``after`` are given, they should be callables with a compatible + signature to the method referred to. They will be called immediately before or after the method + as appropriate, and any return value will be ignored. + + Args: + cls: the class to modify. + name: the name of the method on the class to wrap. + before: a callable that should be called before the method that is being wrapped. + after: a callable that should be called after the method that is being wrapped. + + Raises: + ValueError: if the named method is not defined on the class or any parent class. + """ + # The best time to apply decorators to methods is before they are bound (e.g. by using function + # decorators during the class definition), but if we're making a class decorator, we can't do + # that. We need the actual definition of the method, so we have to dodge the normal output of + # `type.__getattribute__`, which evalutes descriptors if it finds them, unless the name we're + # looking for is defined on `type` itself. In that case, we need the attribute getter to + # correctly return the underlying object, not the one that `type` defines for its own purposes. + attribute_getter = type.__getattribute__ if name in _TYPE_METHODS else object.__getattribute__ + for cls_ in inspect.getmro(cls): + try: + method = attribute_getter(cls_, name) + break + except AttributeError: + pass + else: + raise ValueError(f"Method '{name}' is not defined for class '{cls.__name__}'") + setattr(cls, name, _WrappedMethod(method, before, after)) diff --git a/qiskit_algorithms/utils/lazy_tester.py b/qiskit_algorithms/utils/lazy_tester.py new file mode 100644 index 00000000..4f0d8e5a --- /dev/null +++ b/qiskit_algorithms/utils/lazy_tester.py @@ -0,0 +1,334 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Lazy testers for optional features.""" + +import abc +import contextlib +import functools +import importlib +import subprocess +import typing +from typing import Union, Iterable, Dict, Optional, Callable, Type + +from qiskit.exceptions import MissingOptionalLibraryError +from .classtools import wrap_method + + +class _RequireNow: + """Helper callable that accepts all function signatures and simply calls + :meth:`.LazyDependencyManager.require_now`. This helpful when used with :func:`.wrap_method`, + as the callable needs to be compatible with all signatures and be picklable.""" + + __slots__ = ("_tester", "_feature") + + def __init__(self, tester, feature): + self._tester = tester + self._feature = feature + + def __call__(self, *_args, **_kwargs): + self._tester.require_now(self._feature) + + +class LazyDependencyManager(abc.ABC): + """A mananger for some optional features that are expensive to import, or to verify the + existence of. + + These objects can be used as Booleans, such as ``if x``, and will evaluate ``True`` if the + dependency they test for is available, and ``False`` if not. The presence of the dependency + will only be tested when the Boolean is evaluated, so it can be used as a runtime test in + functions and methods without requiring an import-time test. + + These objects also encapsulate the error handling if their dependency is not present, so you can + do things such as:: + + from qiskit.utils import LazyImportManager + HAS_MATPLOTLIB = LazyImportManager("matplotlib") + + @HAS_MATPLOTLIB.require_in_call + def my_visualisation(): + ... + + def my_other_visualisation(): + # ... some setup ... + HAS_MATPLOTLIB.require_now("my_other_visualisation") + ... + + def my_third_visualisation(): + if HAS_MATPLOTLIB: + from matplotlib import pyplot + else: + ... + + In all of these cases, ``matplotlib`` is not imported until the functions are entered. In the + case of the decorator, ``matplotlib`` is tested for import when the function is called for + the first time. In the second and third cases, the loader attempts to import ``matplotlib`` + when the :meth:`require_now` method is called, or when the Boolean context is evaluated. For + the ``require`` methods, an error is raised if the library is not available. + + This is the base class, which provides the Boolean context checking and error management. The + concrete classes :class:`LazyImportTester` and :class:`LazySubprocessTester` provide convenient + entry points for testing that certain symbols are importable from modules, or certain + command-line tools are available, respectively. + """ + + __slots__ = ("_bool", "_callback", "_name", "_install", "_msg") + + def __init__(self, *, name=None, callback=None, install=None, msg=None): + """ + Args: + name: the name of this optional dependency. + callback: a callback that is called immediately after the availability of the library is + tested with the result. This will only be called once. + install: how to install this optional dependency. Passed to + :class:`.MissingOptionalLibraryError` as the ``pip_install`` parameter. + msg: an extra message to include in the error raised if this is required. + """ + self._bool = None + self._callback = callback + self._name = name + self._install = install + self._msg = msg + + @abc.abstractmethod + def _is_available(self) -> bool: + """Subclasses of :class:`LazyDependencyManager` should override this method to implement the + actual test of availability. This method should return a Boolean, where ``True`` indicates + that the dependency was available. This method will only ever be called once. + + :meta public: + """ + return False + + def __bool__(self): + if self._bool is None: + self._bool = self._is_available() + if self._callback is not None: + self._callback(self._bool) + return self._bool + + @typing.overload + def require_in_call(self, feature_or_callable: Callable) -> Callable: + ... + + @typing.overload + def require_in_call(self, feature_or_callable: str) -> Callable[[Callable], Callable]: + ... + + def require_in_call(self, feature_or_callable): + """Create a decorator for callables that requires that the dependency is available when the + decorated function or method is called. + + Args: + feature_or_callable (str or Callable): the name of the feature that requires these + dependencies. If this function is called directly as a decorator (for example + ``@HAS_X.require_in_call`` as opposed to + ``@HAS_X.require_in_call("my feature")``), then the feature name will be taken to be + the function name, or class and method name as appropriate. + + Returns: + Callable: a decorator that will make its argument require this dependency before it is + called. + """ + if isinstance(feature_or_callable, str): + feature = feature_or_callable + + def decorator(function): + @functools.wraps(function) + def out(*args, **kwargs): + self.require_now(feature) + return function(*args, **kwargs) + + return out + + return decorator + + function = feature_or_callable + feature = ( + getattr(function, "__qualname__", None) + or getattr(function, "__name__", None) + or str(function) + ) + + @functools.wraps(function) + def out(*args, **kwargs): + self.require_now(feature) + return function(*args, **kwargs) + + return out + + @typing.overload + def require_in_instance(self, feature_or_class: Type) -> Type: + ... + + @typing.overload + def require_in_instance(self, feature_or_class: str) -> Callable[[Type], Type]: + ... + + def require_in_instance(self, feature_or_class): + """A class decorator that requires the dependency is available when the class is + initialised. This decorator can be used even if the class does not define an ``__init__`` + method. + + Args: + feature_or_class (str or Type): the name of the feature that requires these + dependencies. If this function is called directly as a decorator (for example + ``@HAS_X.require_in_instance`` as opposed to + ``@HAS_X.require_in_instance("my feature")``), then the feature name will be taken + as the name of the class. + + Returns: + Callable: a class decorator that ensures that the wrapped feature is present if the + class is initialised. + """ + if isinstance(feature_or_class, str): + feature = feature_or_class + + def decorator(class_): + wrap_method(class_, "__init__", before=_RequireNow(self, feature)) + return class_ + + return decorator + + class_ = feature_or_class + feature = ( + getattr(class_, "__qualname__", None) + or getattr(class_, "__name__", None) + or str(class_) + ) + wrap_method(class_, "__init__", before=_RequireNow(self, feature)) + return class_ + + def require_now(self, feature: str): + """Eagerly attempt to import the dependencies in this object, and raise an exception if they + cannot be imported. + + Args: + feature: the name of the feature that is requiring these dependencies. + + Raises: + MissingOptionalLibraryError: if the dependencies cannot be imported. + """ + if self: + return + raise MissingOptionalLibraryError( + libname=self._name, name=feature, pip_install=self._install, msg=self._msg + ) + + @contextlib.contextmanager + def disable_locally(self): + """ + Create a context, during which the value of the dependency manager will be ``False``. This + means that within the context, any calls to this object will behave as if the dependency is + not available, including raising errors. It is valid to call this method whether or not the + dependency has already been evaluated. This is most useful in tests. + """ + previous = self._bool + self._bool = False + try: + yield + finally: + self._bool = previous + + +class LazyImportTester(LazyDependencyManager): + """A lazy dependency tester for importable Python modules. Any required objects will only be + imported at the point that this object is tested for its Boolean value.""" + + __slots__ = ("_modules",) + + def __init__( + self, + name_map_or_modules: Union[str, Dict[str, Iterable[str]], Iterable[str]], + *, + name: Optional[str] = None, + callback: Optional[Callable[[bool], None]] = None, + install: Optional[str] = None, + msg: Optional[str] = None, + ): + """ + Args: + name_map_or_modules: if a name map, then a dictionary where the keys are modules or + packages, and the values are iterables of names to try and import from that + module. It should be valid to write ``from import , , ...``. + If simply a string or iterable of strings, then it should be valid to write + ``import `` for each of them. + + Raises: + ValueError: if no modules are given. + """ + if isinstance(name_map_or_modules, dict): + self._modules = {module: tuple(names) for module, names in name_map_or_modules.items()} + elif isinstance(name_map_or_modules, str): + self._modules = {name_map_or_modules: ()} + else: + self._modules = {module: () for module in name_map_or_modules} + if not self._modules: + raise ValueError("no modules supplied") + if name is not None: + pass + elif len(self._modules) == 1: + (name,) = self._modules.keys() + else: + all_names = tuple(self._modules.keys()) + name = f"{', '.join(all_names[:-1])} and {all_names[-1]}" + super().__init__(name=name, callback=callback, install=install, msg=msg) + + def _is_available(self): + try: + for module, names in self._modules.items(): + imported = importlib.import_module(module) + for name in names: + getattr(imported, name) + except (ImportError, AttributeError): + return False + return True + + +class LazySubprocessTester(LazyDependencyManager): + """A lazy checker that a command-line tool is available. The command will only be run once, at + the point that this object is checked for its Boolean value. + """ + + __slots__ = ("_command",) + + def __init__( + self, + command: Union[str, Iterable[str]], + *, + name: Optional[str] = None, + callback: Optional[Callable[[bool], None]] = None, + install: Optional[str] = None, + msg: Optional[str] = None, + ): + """ + Args: + command: the strings that make up the command to be run. For example, + ``["pdflatex", "-version"]``. + + Raises: + ValueError: if an empty command is given. + """ + self._command = (command,) if isinstance(command, str) else tuple(command) + if not self._command: + raise ValueError("no command supplied") + super().__init__(name=name or self._command[0], callback=callback, install=install, msg=msg) + + def _is_available(self): + try: + subprocess.run( + self._command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + except (OSError, subprocess.SubprocessError): + return False + else: + return True diff --git a/qiskit_algorithms/utils/mitigation/_filters.py b/qiskit_algorithms/utils/mitigation/_filters.py index c5ae2f7e..6e7c467e 100644 --- a/qiskit_algorithms/utils/mitigation/_filters.py +++ b/qiskit_algorithms/utils/mitigation/_filters.py @@ -26,8 +26,6 @@ from copy import deepcopy import numpy as np -from scipy.optimize import minimize -import scipy.linalg as la import qiskit from qiskit import QiskitError @@ -106,6 +104,8 @@ def apply(self, raw_data, method="least_squares"): of the number of calibrated states. """ + from scipy.optimize import minimize + from scipy import linalg as la # check forms of raw_data if isinstance(raw_data, dict): @@ -352,6 +352,8 @@ def apply( Raises: QiskitError: if raw_data is not in a one of the defined forms. """ + from scipy.optimize import minimize + from scipy import linalg as la all_states = count_keys(self.nqubits) num_of_states = 2 ** self.nqubits diff --git a/qiskit_algorithms/utils/optionals.py b/qiskit_algorithms/utils/optionals.py new file mode 100644 index 00000000..3cd7043c --- /dev/null +++ b/qiskit_algorithms/utils/optionals.py @@ -0,0 +1,294 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +.. currentmodule:: qiskit.utils.optionals + +Qiskit Terra, and many of the other Qiskit components, have several features that are enabled only +if certain *optional* dependencies are satisfied. This module is a collection of objects that can +be used to test if certain functionality is available, and optionally raise +:class:`.MissingOptionalLibraryError` if the functionality is not available. + + +Available Testers +================= + +Qiskit Components +----------------- + +.. list-table:: + :widths: 25 75 + + * - .. py:data:: HAS_AER + - :mod:`Qiskit Aer ` provides high-performance simulators for the + quantum circuits constructed within Qiskit Terra. + + * - .. py:data:: HAS_IBMQ + - The :mod:`Qiskit IBMQ Provider ` is used for accessing IBM Quantum + hardware in the IBM cloud. + + * - .. py:data:: HAS_IGNIS + - :mod:`Qiskit Ignis ` provides tools for quantum hardware verification, noise + characterization, and error correction. + + +External Python Libraries +------------------------- + +.. list-table:: + :widths: 25 75 + + * - .. py:data:: HAS_CPLEX + - The `IBM CPLEX Optimizer `__ is a + high-performance mathematical programming solver for linear, mixed-integer and quadratic + programming. It is required by the :class:`.BIPMapping` transpiler pass. + + * - .. py:data:: HAS_CVXPY + - `CVXPY `__ is a Python package for solving convex optimization + problems. It is required for calculating diamond norms with + :func:`.quantum_info.diamond_norm`. + + * - .. py:data:: HAS_DOCPLEX + - `IBM Decision Optimization CPLEX Modelling + `__ is a library for prescriptive + analysis. Like CPLEX, it is required for the :class:`.BIPMapping` transpiler pass. + + * - .. py:data:: HAS_FIXTURES + - The test suite has additional features that are available if the optional `fixtures + `__ module is installed. This generally also needs + :data:`HAS_TESTTOOLS` as well. This is generally only needed for Qiskit developers. + + * - .. py:data:: HAS_IPYTHON + - If `the IPython kernel `__ is available, certain additional + visualisations and line magics are made available. + + * - .. py:data:: HAS_IPYWIDGETS + - Monitoring widgets for jobs running on external backends can be provided if `ipywidgets + `__ is available. + + * - .. py:data:: HAS_JAX + - Some methods of gradient calculation within :mod:`.opflow.gradients` require `JAX + `__ for autodifferentiation. + + * - .. py:data:: HAS_MATPLOTLIB + - Qiskit Terra provides several visualisation tools in the :mod:`.visualization` module. + Almost all of these are built using `Matplotlib `__, which must + be installed in order to use them. + + * - .. py:data:: HAS_NETWORKX + - Internally, Qiskit uses the high-performance `retworkx + `__ library as a core dependency, but sometimes it can + be convenient to convert things into the Python-only `NetworkX `__ + format. There are converter methods on :class:`.DAGCircuit` if NetworkX is present. + + * - .. py:data:: HAS_NLOPT + - `NLOpt `__ is a nonlinear optimisation library, + used by the global optimizers in :mod:`.algorithms.optimizers`. See installation details in + :ref:`installing-nlopt`. + + * - .. py:data:: HAS_PIL + - PIL is a Python image-manipulation library. Qiskit actually uses the `pillow + `__ fork of PIL if it is available when generating + certain visualizations, for example of both :class:`.QuantumCircuit` and + :class:`.DAGCircuit` in certain modes. + + * - .. py:data:: HAS_PYDOT + - For some graph visualisations, Qiskit uses `pydot `__ as an + interface to GraphViz (see :data:`HAS_GRAPHVIZ`). + + * - .. py:data:: HAS_PYLATEX + - Various LaTeX-based visualizations, especially the circuit drawers, need access to the + `pylatexenc `__ project to work correctly. + + * - .. py:data:: HAS_SEABORN + - Qiskit Terra provides several visualisation tools in the :mod:`.visualization` module. Some + of these are built using `Seaborn `__, which must be installed + in order to use them. + + * - .. py:data:: HAS_SKLEARN + - Some of the gradient functions in :mod:`.opflow.gradients` use regularisation methods from + `Scikit Learn `__. + + * - .. py:data:: HAS_SKQUANT + - Some of the optimisers in :mod:`.algorithms.optimizers` are based on those found in `Scikit + Quant `__, which must be installed to use + them. + + * - .. py:data:: HAS_SQSNOBFIT + - `SQSnobFit `__ is a library for the "stable noisy + optimization by branch and fit" algorithm. It is used by the :class:`.SNOBFIT` optimizer. + + * - .. py:data:: HAS_SYMENGINE + - `Symengine `__ is a fast C++ backend for the + symbolic-manipulation library `Sympy `__. Qiskit uses + special methods from Symengine to accelerate its handling of + :class:`~.circuit.Parameter`\\ s if available. + + * - .. py:data:: HAS_TESTTOOLS + - Qiskit Terra's test suite has more advanced functionality available if the optional + `testtools `__ library is installed. This is generally + only needed for Qiskit developers. + + * - .. py:data:: HAS_Z3 + - `Z3 `__ is a theorem prover, used in the + :class:`.CrosstalkAdaptiveSchedule` and :class:`.HoareOptimizer` transpiler passes. + + +External Command-Line Tools +--------------------------- + +.. list-table:: + :widths: 25 75 + + * - .. py:data:: HAS_GRAPHVIZ + - For some graph visualisations, Qiskit uses the `GraphViz `__ + visualisation tool via its ``pydot`` interface (see :data:`HAS_PYDOT`). + + * - .. py:data:: HAS_PDFLATEX + - Visualisation tools that use LaTeX in their output, such as the circuit drawers, require + ``pdflatex`` to be available. You will generally need to ensure that you have a working + LaTeX installation available, and the ``qcircuit.tex`` package. + + * - .. py:data:: HAS_PDFTOCAIRO + - Visualisation tools that convert LaTeX-generated files into rasterised images use the + ``pdftocairo`` tool. This is part of the `Poppler suite of PDF tools + `__. + + +Lazy Checker Classes +==================== + +.. currentmodule:: qiskit.utils + +Each of the lazy checkers is an instance of :class:`.LazyDependencyManager` in one of its two +subclasses: :class:`.LazyImportTester` and :class:`.LazySubprocessTester`. These should be imported +from :mod:`.utils` directly if required, such as:: + + from qiskit.utils import LazyImportTester + +.. autoclass:: qiskit.utils.LazyDependencyManager + :members: + +.. autoclass:: qiskit.utils.LazyImportTester +.. autoclass:: qiskit.utils.LazySubprocessTester +""" + +import logging as _logging + +from .lazy_tester import ( + LazyImportTester as _LazyImportTester, + LazySubprocessTester as _LazySubprocessTester, +) + +_logger = _logging.getLogger(__name__) + +HAS_AER = _LazyImportTester( + "qiskit.providers.aer", + name="Qiskit Aer", + install="pip install qiskit-aer", +) +HAS_IBMQ = _LazyImportTester( + "qiskit.providers.ibmq", + name="IBMQ Provider", + install="pip install qiskit-ibmq-provider", +) +HAS_IGNIS = _LazyImportTester( + "qiskit.ignis", + name="Qiskit Ignis", + install="pip install qiskit-ignis", +) + +HAS_CPLEX = _LazyImportTester( + "cplex", + install="pip install 'qiskit-terra[bip-mapper]'", + msg="This may not be possible for all Python versions and OSes", +) +HAS_CVXPY = _LazyImportTester("cvxpy", install="pip install cvxpy") +HAS_DOCPLEX = _LazyImportTester( + {"docplex": (), "docplex.mp.model": ("Model",)}, + install="pip install 'qiskit-terra[bip-mapper]'", + msg="This may not be possible for all Python versions and OSes", +) +HAS_FIXTURES = _LazyImportTester("fixtures", install="pip install fixtures") +HAS_IPYTHON = _LazyImportTester("IPython", install="pip install ipython") +HAS_IPYWIDGETS = _LazyImportTester("ipywidgets", install="pip install ipywidgets") +HAS_JAX = _LazyImportTester( + {"jax": ("grad", "jit"), "jax.numpy": ()}, + name="jax", + install="pip install jax", +) +HAS_MATPLOTLIB = _LazyImportTester( + ("matplotlib.patches", "matplotlib.pyplot"), + name="matplotlib", + install="pip install matplotlib", +) +HAS_NETWORKX = _LazyImportTester("networkx", install="pip install networkx") + + +def _nlopt_callback(available): + if not available: + return + import nlopt # pylint: disable=import-error + + _logger.info( + "NLopt version: %s.%s.%s", + nlopt.version_major(), + nlopt.version_minor(), + nlopt.version_bugfix(), + ) + + +HAS_NLOPT = _LazyImportTester( + "nlopt", + name="NLopt Optimizer", + callback=_nlopt_callback, + msg="See the documentation of 'qiskit.algorithms.optimizer.nlopts' for installation help", +) +HAS_PIL = _LazyImportTester({"PIL": ("Image",)}, name="pillow", install="pip install pillow") +HAS_PYDOT = _LazyImportTester("pydot", install="pip install pydot") +HAS_PYLATEX = _LazyImportTester( + { + "pylatexenc.latex2text": ("LatexNodes2Text",), + "pylatexenc.latexencode": ("utf8tolatex",), + }, + name="pylatexenc", + install="pip install pylatexenc", +) +HAS_SEABORN = _LazyImportTester("seaborn", install="pip install seaborn") +HAS_SKLEARN = _LazyImportTester( + {"sklearn.linear_model": ("Ridge", "Lasso")}, + name="scikit-learn", + install="pip install scikit-learn", +) +HAS_SKQUANT = _LazyImportTester( + "skquant.opt", + name="scikit-quant", + install="pip install scikit-quant", +) +HAS_SQSNOBFIT = _LazyImportTester("SQSnobFit", install="pip install SQSnobFit") +HAS_SYMENGINE = _LazyImportTester("symengine", install="pip install symengine") +HAS_TESTTOOLS = _LazyImportTester("testtools", install="pip install testtools") +HAS_Z3 = _LazyImportTester("z3", install="pip install z3-solver") + +HAS_GRAPHVIZ = _LazySubprocessTester( + ("dot", "-V"), + name="graphviz", + install="'brew install graphviz' if on Mac, or by downloding it from their website", +) +HAS_PDFLATEX = _LazySubprocessTester( + ("pdflatex", "-version"), + msg="You will likely need to install a full LaTeX distribution for your system", +) +HAS_PDFTOCAIRO = _LazySubprocessTester( + ("pdftocairo", "-v"), + msg="This is part of the 'poppler' set of PDF utilities", +)