From d5e1a8e4f0a6d2471ede7d43c86b78f8f68795d3 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 4 Mar 2022 11:14:19 -0500 Subject: [PATCH] Adopt lazy imports from Terra --- .../algorithms/cplex_optimizer.py | 26 ++-------- .../goemans_williamson_optimizer.py | 29 ++++------- .../algorithms/gurobi_optimizer.py | 29 +++-------- .../applications/bin_packing.py | 23 +++------ .../graph_optimization_application.py | 21 ++------ qiskit_optimization/optionals.py | 49 +++++++++++++++++++ .../problems/quadratic_program.py | 12 +---- qiskit_optimization/translators/gurobipy.py | 49 +++++++------------ requirements.txt | 2 +- test/__init__.py | 6 +-- test/algorithms/test_cplex_optimizer.py | 15 +++--- .../test_goemans_williamson_optimizer.py | 15 ++++-- test/algorithms/test_gurobi_optimizer.py | 8 +-- test/algorithms/test_min_eigen_optimizer.py | 13 +++-- .../algorithms/test_recursive_optimization.py | 9 ++-- test/algorithms/test_warm_start_qaoa.py | 13 +++-- test/applications/test_bin_packing.py | 24 +++++---- test/applications/test_max_cut.py | 19 ++++--- test/converters/test_converters.py | 10 ++-- test/optimization_test_case.py | 21 +------- test/problems/test_quadratic_program.py | 9 ++-- test/translators/test_gurobi.py | 23 +++++---- 22 files changed, 190 insertions(+), 235 deletions(-) create mode 100644 qiskit_optimization/optionals.py diff --git a/qiskit_optimization/algorithms/cplex_optimizer.py b/qiskit_optimization/algorithms/cplex_optimizer.py index 6fe13a1c0..521c2e25a 100644 --- a/qiskit_optimization/algorithms/cplex_optimizer.py +++ b/qiskit_optimization/algorithms/cplex_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020, 2021. +# (C) Copyright IBM 2020, 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 @@ -15,10 +15,9 @@ from typing import Any, Dict, Optional from warnings import warn -from qiskit.exceptions import MissingOptionalLibraryError - from qiskit_optimization.problems.quadratic_program import QuadraticProgram from qiskit_optimization.translators import to_docplex_mp +import qiskit_optimization.optionals as _optionals from .optimization_algorithm import ( OptimizationAlgorithm, OptimizationResult, @@ -26,14 +25,7 @@ ) -try: - from cplex import Cplex # pylint: disable=unused-import - - _HAS_CPLEX = True -except ImportError: - _HAS_CPLEX = False - - +@_optionals.HAS_CPLEX.require_in_instance class CplexOptimizer(OptimizationAlgorithm): """The CPLEX optimizer wrapped as an Qiskit :class:`OptimizationAlgorithm`. @@ -58,24 +50,14 @@ def __init__( disp: Whether to print CPLEX output or not. cplex_parameters: The parameters for CPLEX. See https://www.ibm.com/docs/en/icos/20.1.0?topic=cplex-parameters for details. - - Raises: - MissingOptionalLibraryError: CPLEX is not installed. """ - if not _HAS_CPLEX: - raise MissingOptionalLibraryError( - libname="CPLEX", - name="CplexOptimizer", - pip_install="pip install 'qiskit-optimization[cplex]'", - ) - self._disp = disp self._cplex_parameters = cplex_parameters @staticmethod def is_cplex_installed(): """Returns True if cplex is installed""" - return _HAS_CPLEX + return _optionals.HAS_CPLEX @property def disp(self) -> bool: diff --git a/qiskit_optimization/algorithms/goemans_williamson_optimizer.py b/qiskit_optimization/algorithms/goemans_williamson_optimizer.py index 1bb017260..9027b3ed8 100644 --- a/qiskit_optimization/algorithms/goemans_williamson_optimizer.py +++ b/qiskit_optimization/algorithms/goemans_williamson_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -18,8 +18,8 @@ from typing import Optional, List, Tuple, Union, cast import numpy as np -from qiskit.exceptions import MissingOptionalLibraryError +import qiskit_optimization.optionals as _optionals from .optimization_algorithm import ( OptimizationResult, OptimizationResultStatus, @@ -30,15 +30,6 @@ from ..problems.quadratic_program import QuadraticProgram from ..problems.variable import Variable -try: - import cvxpy as cvx - from cvxpy import DCPError, DGPError, SolverError - - _HAS_CVXPY = True -except ImportError: - _HAS_CVXPY = False - - logger = logging.getLogger(__name__) @@ -78,6 +69,7 @@ def sdp_solution(self) -> Optional[np.ndarray]: return self._sdp_solution +@_optionals.HAS_CVXPY.require_in_instance class GoemansWilliamsonOptimizer(OptimizationAlgorithm): """ Goemans-Williamson algorithm to approximate the max-cut of a problem. @@ -103,16 +95,7 @@ def __init__( unique_cuts: The solve method returns only unique cuts, thus there may be less cuts than ``num_cuts``. seed: A seed value for the random number generator. - - Raises: - MissingOptionalLibraryError: CVXPY is not installed. """ - if not _HAS_CVXPY: - raise MissingOptionalLibraryError( - libname="CVXPY", - name="GoemansWilliamsonOptimizer", - pip_install="pip install 'qiskit-optimization[cvxpy]'", - ) super().__init__() self._num_cuts = num_cuts @@ -148,6 +131,9 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: Returns: cuts: A list of generated cuts. """ + # pylint: disable=import-error + from cvxpy import DCPError, DGPError, SolverError + self._verify_compatibility(problem) min2max = MinimizeToMaximize() @@ -257,6 +243,9 @@ def _solve_max_cut_sdp(self, adj_matrix: np.ndarray) -> np.ndarray: chi: a list of length |V| where the i-th element is +1 or -1, representing which set the it-h vertex is in. Returns None if an error occurs. """ + # pylint: disable=import-error + import cvxpy as cvx + num_vertices = len(adj_matrix) constraints, expr = [], 0 diff --git a/qiskit_optimization/algorithms/gurobi_optimizer.py b/qiskit_optimization/algorithms/gurobi_optimizer.py index 9b0b8f96f..32ed66781 100644 --- a/qiskit_optimization/algorithms/gurobi_optimizer.py +++ b/qiskit_optimization/algorithms/gurobi_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020, 2021. +# (C) Copyright IBM 2020, 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 @@ -12,23 +12,14 @@ """The Gurobi optimizer wrapped to be used within Qiskit's optimization module.""" - -from qiskit.exceptions import MissingOptionalLibraryError - +import qiskit_optimization.optionals as _optionals from ..exceptions import QiskitOptimizationError from ..problems.quadratic_program import QuadraticProgram from ..translators.gurobipy import to_gurobipy from .optimization_algorithm import OptimizationAlgorithm, OptimizationResult -try: - import gurobipy as gp - - _HAS_GUROBI = True -except ImportError: - _HAS_GUROBI = False - - +@_optionals.HAS_GUROBIPY.require_in_instance class GurobiOptimizer(OptimizationAlgorithm): """The Gurobi optimizer wrapped as an Qiskit :class:`OptimizationAlgorithm`. @@ -55,23 +46,13 @@ def __init__(self, disp: bool = False) -> None: Args: disp: Whether to print Gurobi output or not. - - Raises: - MissingOptionalLibraryError: Gurobi is not installed. """ - if not _HAS_GUROBI: - raise MissingOptionalLibraryError( - libname="GUROBI", - name="GurobiOptimizer", - pip_install="pip install qiskit-optimization[gurobi]", - ) - self._disp = disp @staticmethod def is_gurobi_installed(): """Returns True if gurobi is installed""" - return _HAS_GUROBI + return _optionals.HAS_GUROBIPY @property def disp(self) -> bool: @@ -120,6 +101,8 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult: Raises: QiskitOptimizationError: If the problem is incompatible with the optimizer. """ + # pylint: disable=import-error + import gurobipy as gp # convert to Gurobi problem model = to_gurobipy(problem) diff --git a/qiskit_optimization/applications/bin_packing.py b/qiskit_optimization/applications/bin_packing.py index 61bf0b8c8..65155673d 100755 --- a/qiskit_optimization/applications/bin_packing.py +++ b/qiskit_optimization/applications/bin_packing.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -16,20 +16,16 @@ import numpy as np from docplex.mp.model import Model -from qiskit.exceptions import MissingOptionalLibraryError +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import OptimizationResult from qiskit_optimization.problems.quadratic_program import QuadraticProgram from qiskit_optimization.translators import from_docplex_mp from .optimization_application import OptimizationApplication -try: - import matplotlib.pyplot as plt +if _optionals.HAS_MATPLOTLIB: from matplotlib.pyplot import Figure - - _HAS_MATPLOTLIB = True -except ImportError: - _HAS_MATPLOTLIB = False +else: class Figure: # type: ignore """Empty Figure class @@ -109,6 +105,7 @@ def interpret(self, result: Union[OptimizationResult, np.ndarray]) -> List[List[ ] return items_in_bins + @_optionals.HAS_MATPLOTLIB.require_in_call def get_figure(self, result: Union[OptimizationResult, np.ndarray]) -> Figure: """Get plot of the solution of the Bin Packing Problem. @@ -118,15 +115,9 @@ def get_figure(self, result: Union[OptimizationResult, np.ndarray]) -> Figure: Returns: fig: A plot of the solution, where x and y represent the bins and sum of the weights respectively. - Raises: - MissingOptionalLibraryError: if matplotlib is not installed. """ - if not _HAS_MATPLOTLIB: - raise MissingOptionalLibraryError( - libname="matplotlib", - name="GraphOptimizationApplication", - pip_install="pip install 'qiskit-optimization[matplotlib]'", - ) + import matplotlib.pyplot as plt + colors = plt.cm.get_cmap("jet", len(self._weights)) items_in_bins = self.interpret(result) num_bins = len(items_in_bins) diff --git a/qiskit_optimization/applications/graph_optimization_application.py b/qiskit_optimization/applications/graph_optimization_application.py index 17dc1de88..a2b02d7aa 100644 --- a/qiskit_optimization/applications/graph_optimization_application.py +++ b/qiskit_optimization/applications/graph_optimization_application.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -17,19 +17,12 @@ import networkx as nx import numpy as np -from qiskit.exceptions import MissingOptionalLibraryError +import qiskit_optimization.optionals as _optionals from .optimization_application import OptimizationApplication from ..algorithms import OptimizationResult from ..deprecation import DeprecatedType, deprecate_method -try: - import matplotlib as _ - - _HAS_MATPLOTLIB = True -except ImportError: - _HAS_MATPLOTLIB = False - class GraphOptimizationApplication(OptimizationApplication): """ @@ -46,6 +39,7 @@ def __init__(self, graph: Union[nx.Graph, np.ndarray, List]) -> None: # The view of the graph is stored which means the graph can not be changed. self._graph = nx.Graph(graph).copy(as_view=True) + @_optionals.HAS_MATPLOTLIB.require_in_call def draw( self, result: Optional[Union[OptimizationResult, np.ndarray]] = None, @@ -57,16 +51,7 @@ def draw( Args: result: The calculated result for the problem pos: The positions of nodes - Raises: - MissingOptionalLibraryError: if matplotlib is not installed. """ - if not _HAS_MATPLOTLIB: - raise MissingOptionalLibraryError( - libname="matplotlib", - name="GraphOptimizationApplication", - pip_install="pip install 'qiskit-optimization[matplotlib]'", - ) - if result is None: nx.draw(self._graph, pos=pos, with_labels=True) else: diff --git a/qiskit_optimization/optionals.py b/qiskit_optimization/optionals.py new file mode 100644 index 000000000..4293f5144 --- /dev/null +++ b/qiskit_optimization/optionals.py @@ -0,0 +1,49 @@ +# 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. + +""" +Additional optional constants. +""" + +from qiskit.utils import LazyImportTester + +HAS_CPLEX = LazyImportTester( + { + "cplex": ("Cplex",), + }, + name="CPLEX", + install="pip install 'qiskit-optimization[cplex]'", +) + +HAS_CVXPY = LazyImportTester( + { + "cvxpy": ("DCPError", "DGPError", "SolverError"), + }, + name="CVXPY", + install="pip install 'qiskit-optimization[cvx]'", +) + +HAS_GUROBIPY = LazyImportTester( + { + "gurobipy": ("Model",), + }, + name="Gurobi", + install="pip install 'qiskit-optimization[gurobi]'", +) + +HAS_MATPLOTLIB = LazyImportTester( + { + "matplotlib.pyplot": ("Figure",), + }, + name="Matplotlib", + install="pip install 'qiskit-optimization[matplotlib]'", +) diff --git a/qiskit_optimization/problems/quadratic_program.py b/qiskit_optimization/problems/quadratic_program.py index 193da9277..d172942f2 100644 --- a/qiskit_optimization/problems/quadratic_program.py +++ b/qiskit_optimization/problems/quadratic_program.py @@ -23,9 +23,9 @@ from numpy import ndarray from scipy.sparse import spmatrix -from qiskit.exceptions import MissingOptionalLibraryError from qiskit.opflow import OperatorBase +import qiskit_optimization.optionals as _optionals from ..exceptions import QiskitOptimizationError from ..infinity import INFINITY from .constraint import Constraint, ConstraintSense @@ -877,6 +877,7 @@ def export_as_lp_string(self) -> str: return to_docplex_mp(self).export_as_lp_string() + @_optionals.HAS_CPLEX.require_in_call def read_from_lp_file(self, filename: str) -> None: """Loads the quadratic program from a LP file. @@ -885,19 +886,10 @@ def read_from_lp_file(self, filename: str) -> None: Raises: FileNotFoundError: If the file does not exist. - MissingOptionalLibraryError: If CPLEX is not installed. Note: This method requires CPLEX to be installed and present in ``PYTHONPATH``. """ - try: - import cplex # pylint: disable=unused-import - except ImportError as ex: - raise MissingOptionalLibraryError( - libname="CPLEX", - name="QuadraticProgram.read_from_lp_file", - pip_install="pip install 'qiskit-optimization[cplex]'", - ) from ex def _parse_problem_name(filename: str) -> str: # Because docplex model reader uses the base name as model name, diff --git a/qiskit_optimization/translators/gurobipy.py b/qiskit_optimization/translators/gurobipy.py index 20de2e4d1..30008a1d8 100644 --- a/qiskit_optimization/translators/gurobipy.py +++ b/qiskit_optimization/translators/gurobipy.py @@ -14,23 +14,7 @@ from typing import cast -try: - import gurobipy as gp - from gurobipy import Model - - _HAS_GUROBI = True -except ImportError: - _HAS_GUROBI = False - - class Model: # type: ignore - """Empty Model class - Replacement if gurobipy.Model is not present. - """ - - pass - - -from qiskit.exceptions import MissingOptionalLibraryError +import qiskit_optimization.optionals as _optionals from qiskit_optimization.exceptions import QiskitOptimizationError from qiskit_optimization.problems.constraint import Constraint from qiskit_optimization.problems.quadratic_objective import QuadraticObjective @@ -38,16 +22,20 @@ class Model: # type: ignore from qiskit_optimization.problems.quadratic_program import QuadraticProgram +if _optionals.HAS_GUROBIPY: + # pylint: disable=import-error,no-name-in-module + from gurobipy import Model +else: -def _check_gurobipy_is_installed(name: str): - if not _HAS_GUROBI: - raise MissingOptionalLibraryError( - libname="GUROBI", - name=name, - pip_install="pip install qiskit-optimization[gurobi]", - ) + class Model: # type: ignore + """Empty Model class + Replacement if gurobipy.Model is not present. + """ + + pass +@_optionals.HAS_GUROBIPY.require_in_call def to_gurobipy(quadratic_program: QuadraticProgram) -> Model: """Returns a gurobipy model corresponding to a quadratic program. @@ -59,12 +47,11 @@ def to_gurobipy(quadratic_program: QuadraticProgram) -> Model: Raises: QiskitOptimizationError: if non-supported elements (should never happen). - MissingOptionalLibraryError: if gurobipy is not installed. """ - - _check_gurobipy_is_installed("to_gurobipy") - # initialize model + # pylint: disable=import-error + import gurobipy as gp + mdl = gp.Model(quadratic_program.name) # add variables @@ -145,6 +132,7 @@ def to_gurobipy(quadratic_program: QuadraticProgram) -> Model: return mdl +@_optionals.HAS_GUROBIPY.require_in_call def from_gurobipy(model: Model) -> QuadraticProgram: """Translate a gurobipy model into a quadratic program. @@ -162,10 +150,9 @@ def from_gurobipy(model: Model) -> QuadraticProgram: Raises: QiskitOptimizationError: if the model contains unsupported elements. - MissingOptionalLibraryError: if gurobipy is not installed. """ - - _check_gurobipy_is_installed("from_gurobipy") + # pylint: disable=import-error + import gurobipy as gp if not isinstance(model, Model): raise QiskitOptimizationError(f"The model is not compatible: {model}") diff --git a/requirements.txt b/requirements.txt index 017ec3c5a..7f3226a7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit-terra>=0.19.1 +qiskit-terra>=0.20.0 scipy>=1.4 numpy>=1.17 docplex>=2.21.207 diff --git a/test/__init__.py b/test/__init__.py index 3d2502a61..3d5d87dde 100755 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -12,6 +12,6 @@ """ Optimization test packages """ -from .optimization_test_case import QiskitOptimizationTestCase, requires_extra_library +from .optimization_test_case import QiskitOptimizationTestCase -__all__ = ["QiskitOptimizationTestCase", "requires_extra_library"] +__all__ = ["QiskitOptimizationTestCase"] diff --git a/test/algorithms/test_cplex_optimizer.py b/test/algorithms/test_cplex_optimizer.py index e735505c2..57c05923d 100644 --- a/test/algorithms/test_cplex_optimizer.py +++ b/test/algorithms/test_cplex_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -13,27 +13,28 @@ """ Test Cplex Optimizer """ import unittest -from test.optimization_test_case import QiskitOptimizationTestCase, requires_extra_library +from test.optimization_test_case import QiskitOptimizationTestCase import numpy as np from ddt import data, ddt +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import CplexOptimizer, OptimizationResultStatus from qiskit_optimization.problems import QuadraticProgram @ddt class TestCplexOptimizer(QiskitOptimizationTestCase): - """Cplex Optimizer Tests.""" + """CPLEX Optimizer Tests.""" @data( ("op_ip1.lp", [0, 2], 6), ("op_mip1.lp", [0, 1, 1], 5.5), ("op_lp1.lp", [0.25, 1.75], 5.8750), ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_cplex_optimizer(self, config): - """Cplex Optimizer Test""" + """CPLEX Optimizer Test""" cplex_optimizer = CplexOptimizer( disp=False, cplex_parameters={"threads": 1, "randomseed": 1} ) @@ -58,9 +59,9 @@ def test_cplex_optimizer(self, config): ("op_mip1.lp", [0, 1, 1], 5.5), ("op_lp1.lp", [0.25, 1.75], 5.8750), ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_cplex_optimizer_no_solution(self, config): - """Cplex Optimizer Test if no solution is found""" + """CPLEX Optimizer Test if no solution is found""" cplex_optimizer = CplexOptimizer(disp=False, cplex_parameters={"dettimelimit": 0}) # unpack configuration filename, _, _ = config diff --git a/test/algorithms/test_goemans_williamson_optimizer.py b/test/algorithms/test_goemans_williamson_optimizer.py index a7110831b..9386211ab 100644 --- a/test/algorithms/test_goemans_williamson_optimizer.py +++ b/test/algorithms/test_goemans_williamson_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -11,10 +11,13 @@ # that they have been altered from the originals. """ Test Goemans-Williamson optimizer. """ -from test import QiskitOptimizationTestCase, requires_extra_library + +import unittest +from test import QiskitOptimizationTestCase import numpy as np +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms.goemans_williamson_optimizer import ( GoemansWilliamsonOptimizer, GoemansWilliamsonOptimizationResult, @@ -37,7 +40,7 @@ def setUp(self) -> None: ] ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CVXPY, "CVXPY not available.") def test_all_cuts(self): """Basic test of the Goemans-Williamson optimizer.""" @@ -59,7 +62,7 @@ def test_all_cuts(self): self.assertIsNotNone(results.samples) self.assertEqual(3, len(results.samples)) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CVXPY, "CVXPY not available.") def test_minimization_problem(self): """Tests the optimizer with a minimization problem""" optimizer = GoemansWilliamsonOptimizer(num_cuts=10, seed=0) @@ -74,3 +77,7 @@ def test_minimization_problem(self): np.testing.assert_almost_equal([0, 1, 1, 0], results.x, 3) np.testing.assert_almost_equal(4, results.fval, 3) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/algorithms/test_gurobi_optimizer.py b/test/algorithms/test_gurobi_optimizer.py index 7ccf5ad13..069f2b4bd 100644 --- a/test/algorithms/test_gurobi_optimizer.py +++ b/test/algorithms/test_gurobi_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -13,11 +13,12 @@ """ Test Gurobi Optimizer """ import unittest -from test.optimization_test_case import QiskitOptimizationTestCase, requires_extra_library +from test.optimization_test_case import QiskitOptimizationTestCase from ddt import data, ddt import numpy as np +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import GurobiOptimizer from qiskit_optimization.problems import QuadraticProgram @@ -31,7 +32,8 @@ class TestGurobiOptimizer(QiskitOptimizationTestCase): ("op_mip1.lp", [0, 1, 1], 5.5), ("op_lp1.lp", [0.25, 1.75], 5.8750), ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GUROBIPY, "Gurobi not available.") + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_gurobi_optimizer(self, config): """Gurobi Optimizer Test""" # unpack configuration diff --git a/test/algorithms/test_min_eigen_optimizer.py b/test/algorithms/test_min_eigen_optimizer.py index b29c1e79c..257fe7fe4 100644 --- a/test/algorithms/test_min_eigen_optimizer.py +++ b/test/algorithms/test_min_eigen_optimizer.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -13,10 +13,8 @@ """ Test Min Eigen Optimizer """ import unittest -from test.optimization_test_case import ( - QiskitOptimizationTestCase, - requires_extra_library, -) +from test.optimization_test_case import QiskitOptimizationTestCase + from test.runtime.fake_vqeruntime import FakeVQERuntimeProvider, FakeQAOARuntimeProvider import numpy as np @@ -27,6 +25,7 @@ from qiskit.circuit.library import TwoLocal from qiskit.providers.basicaer import QasmSimulatorPy from qiskit.utils import QuantumInstance, algorithm_globals +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import ( CplexOptimizer, MinimumEigenOptimizer, @@ -98,7 +97,7 @@ def setUp(self): ("qaoa", "statevector_simulator", "op_ip1.lp"), ("qaoa", "qasm_simulator", "op_ip1.lp"), ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_min_eigen_optimizer(self, config): """Min Eigen Optimizer Test""" try: @@ -138,7 +137,7 @@ def test_min_eigen_optimizer(self, config): ("op_ip1.lp", -470, 12, OptimizationResultStatus.SUCCESS), ("op_ip1.lp", np.inf, None, OptimizationResultStatus.FAILURE), ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_min_eigen_optimizer_with_filter(self, config): """Min Eigen Optimizer Test""" try: diff --git a/test/algorithms/test_recursive_optimization.py b/test/algorithms/test_recursive_optimization.py index 7d7357d88..53e206b7b 100755 --- a/test/algorithms/test_recursive_optimization.py +++ b/test/algorithms/test_recursive_optimization.py @@ -13,7 +13,7 @@ """Test Recursive Min Eigen Optimizer.""" import unittest -from test import QiskitOptimizationTestCase, requires_extra_library +from test import QiskitOptimizationTestCase import numpy as np @@ -22,6 +22,7 @@ from qiskit.algorithms import NumPyMinimumEigensolver, QAOA +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import ( MinimumEigenOptimizer, CplexOptimizer, @@ -44,7 +45,7 @@ class TestRecursiveMinEigenOptimizer(QiskitOptimizationTestCase): """Recursive Min Eigen Optimizer Tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_recursive_min_eigen_optimizer(self): """Test the recursive minimum eigen optimizer.""" filename = "op_ip1.lp" @@ -73,7 +74,7 @@ def test_recursive_min_eigen_optimizer(self): np.testing.assert_array_almost_equal(cplex_result.x, result.x, 4) self.assertAlmostEqual(cplex_result.fval, result.fval) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_recursive_history(self): """Tests different options for history.""" filename = "op_ip1.lp" @@ -127,7 +128,7 @@ def test_recursive_history(self): self.assertGreater(len(result.history[0]), 1) self.assertIsNotNone(result.history[1]) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_recursive_warm_qaoa(self): """Test the recursive optimizer with warm start qaoa.""" seed = 1234 diff --git a/test/algorithms/test_warm_start_qaoa.py b/test/algorithms/test_warm_start_qaoa.py index 6b368c284..39f0dba0c 100644 --- a/test/algorithms/test_warm_start_qaoa.py +++ b/test/algorithms/test_warm_start_qaoa.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -11,7 +11,9 @@ # that they have been altered from the originals. """ Test warm start QAOA optimizer. """ -from test import QiskitOptimizationTestCase, requires_extra_library + +import unittest +from test import QiskitOptimizationTestCase import numpy as np @@ -19,6 +21,7 @@ from qiskit import BasicAer from qiskit.algorithms import QAOA +import qiskit_optimization.optionals as _optionals from qiskit_optimization.algorithms import SlsqpOptimizer from qiskit_optimization.algorithms.goemans_williamson_optimizer import ( GoemansWilliamsonOptimizer, @@ -34,7 +37,7 @@ class TestWarmStartQAOAOptimizer(QiskitOptimizationTestCase): """Tests for the warm start QAOA optimizer.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CVXPY, "CVXPY not available.") def test_max_cut(self): """Basic test on the max cut problem.""" graph = np.array( @@ -125,3 +128,7 @@ def test_simple_qubo(self): np.testing.assert_almost_equal([0, 1], result_warm.x, 3) self.assertIsNotNone(result_warm.fval) np.testing.assert_almost_equal(1, result_warm.fval, 3) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/applications/test_bin_packing.py b/test/applications/test_bin_packing.py index 19bca915c..e2204a44f 100755 --- a/test/applications/test_bin_packing.py +++ b/test/applications/test_bin_packing.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -11,8 +11,11 @@ # that they have been altered from the originals. """Test bin packing class""" -from test.optimization_test_case import QiskitOptimizationTestCase, requires_extra_library +import unittest +from test.optimization_test_case import QiskitOptimizationTestCase + +import qiskit_optimization.optionals as _optionals from qiskit_optimization import QuadraticProgram from qiskit_optimization.algorithms import OptimizationResult, OptimizationResultStatus from qiskit_optimization.applications.bin_packing import BinPacking @@ -90,22 +93,17 @@ def test_max_number_of_bins(self): self.assertEqual(lin[3].linear.to_dict(), {2: 16.0, 4: 9.0, 6: 23.0, 0: -40.0}) self.assertEqual(lin[4].linear.to_dict(), {3: 16.0, 5: 9.0, 7: 23.0, 1: -40.0}) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_MATPLOTLIB, "Matplotlib not available.") def test_figure(self): """Test the plot of the Bin Packing Problem is properly generated.""" - try: - from matplotlib.pyplot import Figure - except ImportError: - - class Figure: - """Empty Figure class - Replacement Figure for when matplotlib is not present. - """ - - pass + from matplotlib.pyplot import Figure bin_packing = BinPacking( weights=self.weights, max_weight=self.max_weight, ) self.assertIsInstance(bin_packing.get_figure(self.result), Figure) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/applications/test_max_cut.py b/test/applications/test_max_cut.py index 39cf0c929..21ad748bd 100644 --- a/test/applications/test_max_cut.py +++ b/test/applications/test_max_cut.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -11,11 +11,13 @@ # that they have been altered from the originals. """ Test GraphPartinioning class""" + +import unittest from test.optimization_test_case import QiskitOptimizationTestCase import networkx as nx -from qiskit.exceptions import MissingOptionalLibraryError +import qiskit_optimization.optionals as _optionals from qiskit_optimization import QuadraticProgram from qiskit_optimization.algorithms import OptimizationResult, OptimizationResultStatus from qiskit_optimization.applications.max_cut import Maxcut @@ -78,14 +80,15 @@ def test_node_color(self): maxcut = Maxcut(self.graph) self.assertEqual(maxcut._node_color(self.result), ["b", "b", "r", "r"]) - def test_draw(self): + @unittest.skipIf(_optionals.HAS_MATPLOTLIB, "Matplotlib is available.") + def test_draw_without_maxplotlin(self): """Test whether draw raises an error if matplotlib is not installed""" maxcut = Maxcut(self.graph) - try: - import matplotlib as _ + from qiskit.exceptions import MissingOptionalLibraryError + with self.assertRaises(MissingOptionalLibraryError): maxcut.draw() - except ImportError: - with self.assertRaises(MissingOptionalLibraryError): - maxcut.draw() + +if __name__ == "__main__": + unittest.main() diff --git a/test/converters/test_converters.py b/test/converters/test_converters.py index e46ea49a3..7290f3877 100644 --- a/test/converters/test_converters.py +++ b/test/converters/test_converters.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020, 2021. +# (C) Copyright IBM 2020, 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 @@ -13,15 +13,13 @@ """ Test Converters """ import unittest -from test.optimization_test_case import ( - QiskitOptimizationTestCase, - requires_extra_library, -) +from test.optimization_test_case import QiskitOptimizationTestCase import numpy as np from docplex.mp.model import Model from qiskit.algorithms import NumPyMinimumEigensolver from qiskit.opflow import Z, I +import qiskit_optimization.optionals as _optionals from qiskit_optimization import QuadraticProgram, QiskitOptimizationError from qiskit_optimization.algorithms import ( MinimumEigenOptimizer, @@ -474,7 +472,7 @@ def test_ising_to_quadraticprogram_quadratic(self): quadratic.objective.quadratic.coefficients.toarray(), quadratic_matrix ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_continuous_variable_decode(self): """Test decode func of IntegerToBinaryConverter for continuous variables""" mdl = Model("test_continuous_varable_decode") diff --git a/test/optimization_test_case.py b/test/optimization_test_case.py index a44267270..660b14627 100755 --- a/test/optimization_test_case.py +++ b/test/optimization_test_case.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2018, 2021. +# (C) Copyright IBM 2018, 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 @@ -20,7 +20,6 @@ import os import unittest import time -from qiskit.exceptions import MissingOptionalLibraryError # disable deprecation warnings that can cause log output overflow # pylint: disable=unused-argument @@ -34,24 +33,6 @@ def _noop(*args, **kargs): # warnings.warn = _noop -def requires_extra_library(test_item): - """Decorator that skips test if an extra library is not available - Args: - test_item (callable): function to be decorated. - Returns: - callable: the decorated function. - """ - - def wrapper(self, *args): - try: - test_item(self, *args) - except MissingOptionalLibraryError as ex: - self.skipTest(str(ex)) - return wrapper - - return wrapper - - class QiskitOptimizationTestCase(unittest.TestCase, ABC): """Optimization Test Case""" diff --git a/test/problems/test_quadratic_program.py b/test/problems/test_quadratic_program.py index a819ff490..55e7ab9bb 100644 --- a/test/problems/test_quadratic_program.py +++ b/test/problems/test_quadratic_program.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020, 2021. +# (C) Copyright IBM 2020, 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 @@ -15,11 +15,12 @@ import tempfile import unittest from os import path -from test.optimization_test_case import QiskitOptimizationTestCase, requires_extra_library +from test.optimization_test_case import QiskitOptimizationTestCase from docplex.mp.model import DOcplexException from qiskit.opflow import PauliSumOp +import qiskit_optimization.optionals as _optionals from qiskit_optimization import INFINITY, QiskitOptimizationError, QuadraticProgram from qiskit_optimization.problems import Constraint, QuadraticObjective, Variable, VarType @@ -740,7 +741,7 @@ def test_empty_objective(self): q_p.binary_var_list(3) _ = q_p.objective.evaluate_gradient({}) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_read_from_lp_file(self): """test read lp file""" try: @@ -1003,7 +1004,7 @@ def test_feasibility(self): self.assertEqual("c3", constraints[1].name) self.assertEqual("c5", constraints[2].name) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_CPLEX, "CPLEX not available.") def test_quadratic_program_element_when_loaded_from_source(self): """Test QuadraticProgramElement when QuadraticProgram is loaded from an external source""" with self.subTest("from_ising"): diff --git a/test/translators/test_gurobi.py b/test/translators/test_gurobi.py index 04fc7a214..311fa584d 100644 --- a/test/translators/test_gurobi.py +++ b/test/translators/test_gurobi.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2021. +# (C) Copyright IBM 2021, 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 @@ -12,8 +12,9 @@ """Test from_gurobipy and to_gurobipy""" -from test.optimization_test_case import QiskitOptimizationTestCase, requires_extra_library -from qiskit.exceptions import MissingOptionalLibraryError +import unittest +from test.optimization_test_case import QiskitOptimizationTestCase +import qiskit_optimization.optionals as _optionals from qiskit_optimization.exceptions import QiskitOptimizationError from qiskit_optimization.problems import Constraint, QuadraticProgram from qiskit_optimization.translators.gurobipy import from_gurobipy, to_gurobipy @@ -22,7 +23,7 @@ class TestGurobiTranslator(QiskitOptimizationTestCase): """Test from_gurobipy and to_gurobipy""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GUROBIPY, "Gurobi not available.") def test_from_and_to(self): """test from_gurobipy and to_gurobipy""" q_p = QuadraticProgram("test") @@ -35,14 +36,8 @@ def test_from_and_to(self): q_p2 = from_gurobipy(to_gurobipy(q_p)) self.assertEqual(q_p.export_as_lp_string(), q_p2.export_as_lp_string()) - try: - import gurobipy as gp - except ImportError as ex: - raise MissingOptionalLibraryError( - libname="GUROBI", - name="GurobiOptimizer", - pip_install="pip install qiskit-optimization[gurobi]", - ) from ex + # pylint: disable=import-error + import gurobipy as gp mod = gp.Model("test") x = mod.addVar(vtype=gp.GRB.BINARY, name="x") @@ -92,3 +87,7 @@ def test_from_and_to(self): self.assertDictEqual(c.linear.to_dict(use_name=True), {"C2": -1}) self.assertDictEqual(c.quadratic.to_dict(use_name=True), {("C0", "C1"): 1}) self.assertEqual(c.sense, senses[i]) + + +if __name__ == "__main__": + unittest.main()