From 5f5f1d200b425df2ce4848da52c6bf1e7654cabf Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Thu, 3 Feb 2022 19:06:49 -0500 Subject: [PATCH 1/4] Adopt lazy imports from Terra --- .../electronic_structure_molecule_driver.py | 5 +- .../gaussiand/gaussian_forces_driver.py | 18 +--- .../gaussiand/gaussian_log_driver.py | 18 +--- .../gaussiand/gaussian_utils.py | 33 ++----- .../gaussiand/gaussiandriver.py | 17 +--- .../second_quantization/psi4d/psi4driver.py | 32 ++----- .../pyquanted/pyquantedriver.py | 40 ++------- .../second_quantization/pyscfd/pyscfdriver.py | 41 ++------- .../vibrational_structure_molecule_driver.py | 5 +- qiskit_nature/optionals.py | 90 +++++++++++++++++++ .../lattice/lattices/lattice.py | 10 +-- test/__init__.py | 6 +- .../test_numpy_eigensolver_factory.py | 7 +- .../test_excited_states_solvers.py | 5 +- .../ground_state_solvers/test_adapt_vqe.py | 5 +- .../test_advanced_ucc_variants.py | 5 +- .../pes_samplers/test_bopes_sampler.py | 9 +- .../gaussiand/test_driver_gaussian.py | 9 +- .../gaussiand/test_driver_gaussian_extra.py | 22 +---- .../gaussiand/test_driver_gaussian_forces.py | 9 +- .../test_driver_gaussian_from_mat.py | 21 +---- .../gaussiand/test_driver_gaussian_log.py | 7 +- .../gaussiand/test_driver_methods_gaussian.py | 6 +- .../psi4d/test_driver_methods_psi4.py | 6 +- .../psi4d/test_driver_psi4.py | 9 +- .../psi4d/test_driver_psi4_extra.py | 8 +- .../pyquanted/test_driver_methods_pyquante.py | 6 +- .../pyquanted/test_driver_pyquante.py | 9 +- .../pyscfd/test_driver_methods_pyscf.py | 6 +- .../pyscfd/test_driver_pyscf.py | 9 +- .../test_molecule_driver.py | 26 ++++-- test/nature_test_case.py | 21 +---- .../builders/test_hopping_ops_builder.py | 8 +- .../test_electronic_structure_problem.py | 8 +- test/test_readme_sample.py | 5 +- 35 files changed, 240 insertions(+), 301 deletions(-) create mode 100644 qiskit_nature/optionals.py diff --git a/qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py b/qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py index 78af4c1162..0c64d07b1e 100644 --- a/qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.py +++ b/qiskit_nature/drivers/second_quantization/electronic_structure_molecule_driver.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 @@ -81,7 +81,8 @@ def driver_class_from_type( raise MissingOptionalLibraryError( libname=driver_type, name="ElectronicStructureDriverType" ) - driver_class.check_installed() + # instantiating the object will check if the driver is installed + _ = driver_class() driver_class.check_method_supported(method) logger.debug("%s found from type %s.", driver_class.__name__, driver_type.value) diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py index 28506ad150..8da7b13218 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.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 @@ -18,10 +18,10 @@ from qiskit_nature.properties.second_quantization.vibrational import ( VibrationalStructureDriverResult, ) +import qiskit_nature.optionals as _optionals from ...units_type import UnitsType from ..vibrational_structure_driver import VibrationalStructureDriver from ...molecule import Molecule -from .gaussian_utils import check_valid from .gaussian_log_driver import GaussianLogDriver from .gaussian_log_result import GaussianLogResult @@ -75,9 +75,10 @@ def __init__( # If running from a jcf we need Gaussian™ 16 so check if we have a # valid install. if self._logfile is None: - check_valid() + _optionals.HAS_GAUSSIAN.require_now("GaussianForcesDriver __init__") @staticmethod + @_optionals.HAS_GAUSSIAN.require_in_call("GaussianForcesDriver from_molecule") def from_molecule( molecule: Molecule, basis: str = "sto-3g", @@ -98,7 +99,6 @@ def from_molecule( """ # Ignore kwargs parameter for this driver del driver_kwargs - GaussianForcesDriver.check_installed() basis = GaussianForcesDriver.to_driver_basis(basis) if molecule.units == UnitsType.ANGSTROM: @@ -130,16 +130,6 @@ def to_driver_basis(basis: str) -> str: return "sto-3g" return basis - @staticmethod - def check_installed() -> None: - """ - Checks if Gaussian is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - check_valid() - def run(self) -> VibrationalStructureDriverResult: if self._logfile is not None: glr = GaussianLogResult(self._logfile) diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_log_driver.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_log_driver.py index 6ff3e445c2..96b73b3075 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_log_driver.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_log_driver.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 @@ -16,14 +16,16 @@ import logging from qiskit_nature import QiskitNatureError +import qiskit_nature.optionals as _optionals from ..base_driver import BaseDriver -from .gaussian_utils import check_valid, run_g16 +from .gaussian_utils import run_g16 from .gaussian_log_result import GaussianLogResult logger = logging.getLogger(__name__) +@_optionals.HAS_GAUSSIAN.require_in_instance class GaussianLogDriver(BaseDriver): """Gaussian™ 16 log driver. @@ -47,8 +49,6 @@ def __init__(self, jcf: Union[str, List[str]]) -> None: Raises: QiskitNatureError: Invalid Input """ - GaussianLogDriver.check_installed() - if not isinstance(jcf, list) and not isinstance(jcf, str): raise QiskitNatureError(f"Invalid input for Gaussian Log Driver '{jcf}'") @@ -58,16 +58,6 @@ def __init__(self, jcf: Union[str, List[str]]) -> None: self._jcf = jcf super().__init__() - @staticmethod - def check_installed() -> None: - """ - Checks if Gaussian is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - check_valid() - def run(self) -> GaussianLogResult: # type: ignore """Runs the driver to produce a result given the supplied job control file. diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_utils.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_utils.py index bd9a59bbf7..d613c821e6 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_utils.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_utils.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 @@ -14,32 +14,11 @@ import logging from subprocess import Popen, PIPE -from shutil import which -from qiskit.exceptions import MissingOptionalLibraryError from qiskit_nature import QiskitNatureError +import qiskit_nature.optionals as _optionals logger = logging.getLogger(__name__) -_GAUSSIAN_16 = "g16" -_GAUSSIAN_16_DESC = "Gaussian 16" - -_G16PROG = which(_GAUSSIAN_16) - - -def check_valid() -> None: - """ - Checks if Gaussian is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - if _G16PROG is None: - raise MissingOptionalLibraryError( - libname=_GAUSSIAN_16, - name=_GAUSSIAN_16_DESC, - msg="Please check that it is installed correctly", - ) - def run_g16(cfg: str) -> str: """ @@ -58,14 +37,16 @@ def run_g16(cfg: str) -> str: """ process = None try: - with Popen(_GAUSSIAN_16, stdin=PIPE, stdout=PIPE, universal_newlines=True) as process: + with Popen( + _optionals.GAUSSIAN_16, stdin=PIPE, stdout=PIPE, universal_newlines=True + ) as process: stdout, _ = process.communicate(cfg) process.wait() except Exception as ex: if process is not None: process.kill() - raise QiskitNatureError(f"{_GAUSSIAN_16_DESC} run has failed") from ex + raise QiskitNatureError(f"{_optionals.GAUSSIAN_16_DESC} run has failed") from ex if process.returncode != 0: errmsg = "" @@ -78,7 +59,7 @@ def run_g16(cfg: str) -> str: logger.error(lines[i]) errmsg += lines[i] + "\n" raise QiskitNatureError( - f"{_GAUSSIAN_16_DESC} process return code {process.returncode}\n{errmsg}" + f"{_optionals.GAUSSIAN_16_DESC} process return code {process.returncode}\n{errmsg}" ) all_text = "" diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py index a3a296253f..4f15c78929 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py @@ -25,9 +25,10 @@ from qiskit_nature import QiskitNatureError from qiskit_nature.constants import PERIODIC_TABLE from qiskit_nature.properties.second_quantization.electronic import ElectronicStructureDriverResult +import qiskit_nature.optionals as _optionals from ...qmolecule import QMolecule -from .gaussian_utils import check_valid, run_g16 +from .gaussian_utils import run_g16 from ..electronic_structure_driver import ElectronicStructureDriver, MethodType from ...molecule import Molecule from ...units_type import UnitsType @@ -36,6 +37,7 @@ logger = logging.getLogger(__name__) +@_optionals.HAS_GAUSSIAN.require_in_instance class GaussianDriver(ElectronicStructureDriver): """ Qiskit Nature driver using the Gaussian™ 16 program. @@ -62,7 +64,6 @@ def __init__( QiskitNatureError: Invalid Input """ super().__init__() - GaussianDriver.check_installed() if not isinstance(config, str) and not isinstance(config, list): raise QiskitNatureError(f"Invalid config for Gaussian Driver '{config}'") @@ -72,6 +73,7 @@ def __init__( self._config = config @staticmethod + @_optionals.HAS_GAUSSIAN.require_in_call("GaussianDriver from_molecule") def from_molecule( molecule: Molecule, basis: str = "sto-3g", @@ -91,7 +93,6 @@ def from_molecule( """ # Ignore kwargs parameter for this driver del driver_kwargs - GaussianDriver.check_installed() GaussianDriver.check_method_supported(method) basis = GaussianDriver.to_driver_basis(basis) @@ -124,16 +125,6 @@ def to_driver_basis(basis: str) -> str: return "sto-3g" return basis - @staticmethod - def check_installed() -> None: - """ - Checks if Gaussian is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - check_valid() - @staticmethod def check_method_supported(method: MethodType) -> None: """ diff --git a/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py b/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py index 0f6f0229d8..e4d6f54ea4 100644 --- a/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py +++ b/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.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 @@ -19,12 +19,11 @@ import tempfile import warnings from pathlib import Path -from shutil import which from typing import Union, List, Optional, Any, Dict -from qiskit.exceptions import MissingOptionalLibraryError from qiskit_nature import QiskitNatureError from qiskit_nature.properties.second_quantization.electronic import ElectronicStructureDriverResult +import qiskit_nature.optionals as _optionals from ...qmolecule import QMolecule from ..electronic_structure_driver import ElectronicStructureDriver, MethodType @@ -34,11 +33,8 @@ logger = logging.getLogger(__name__) -PSI4 = "psi4" - -PSI4_APP = which(PSI4) - +@_optionals.HAS_PSI4.require_in_instance class PSI4Driver(ElectronicStructureDriver): """ Qiskit Nature driver using the PSI4 program. @@ -61,7 +57,6 @@ def __init__( QiskitNatureError: Invalid Input """ super().__init__() - PSI4Driver.check_installed() if not isinstance(config, str) and not isinstance(config, list): raise QiskitNatureError(f"Invalid config for PSI4 Driver '{config}'") @@ -71,6 +66,7 @@ def __init__( self._config = config @staticmethod + @_optionals.HAS_PSI4.require_in_call("PSI4Driver from_molecule") def from_molecule( molecule: Molecule, basis: str = "sto3g", @@ -91,7 +87,6 @@ def from_molecule( """ # Ignore kwargs parameter for this driver del driver_kwargs - PSI4Driver.check_installed() PSI4Driver.check_method_supported(method) basis = PSI4Driver.to_driver_basis(basis) @@ -124,17 +119,6 @@ def to_driver_basis(basis: str) -> str: return "sto-3g" return basis - @staticmethod - def check_installed() -> None: - """ - Checks if PSI4 is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - if PSI4_APP is None: - raise MissingOptionalLibraryError(libname="PSI4", name="PSI4Driver") - @staticmethod def check_method_supported(method: MethodType) -> None: """ @@ -228,7 +212,7 @@ def _run_psi4(input_file, output_file): process = None try: with subprocess.Popen( - [PSI4, input_file, output_file], + [_optionals.PSI4, input_file, output_file], stdout=subprocess.PIPE, universal_newlines=True, ) as process: @@ -238,7 +222,7 @@ def _run_psi4(input_file, output_file): if process is not None: process.kill() - raise QiskitNatureError(f"{PSI4} run has failed") from ex + raise QiskitNatureError(f"{_optionals.PSI4} run has failed") from ex if process.returncode != 0: errmsg = "" @@ -247,4 +231,6 @@ def _run_psi4(input_file, output_file): for i, _ in enumerate(lines): logger.error(lines[i]) errmsg += lines[i] + "\n" - raise QiskitNatureError(f"{PSI4} process return code {process.returncode}\n{errmsg}") + raise QiskitNatureError( + f"{_optionals.PSI4} process return code {process.returncode}\n{errmsg}" + ) diff --git a/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py b/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py index 2a6356f5ae..bb1e4c63a3 100644 --- a/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py +++ b/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py @@ -12,7 +12,6 @@ """ PyQuante Driver """ -import importlib import inspect import logging import re @@ -21,7 +20,6 @@ import numpy as np -from qiskit.exceptions import MissingOptionalLibraryError from qiskit.utils.validation import validate_min from qiskit_nature import QiskitNatureError @@ -43,6 +41,7 @@ OneBodyElectronicIntegrals, TwoBodyElectronicIntegrals, ) +import qiskit_nature.optionals as _optionals from ..electronic_structure_driver import ElectronicStructureDriver, MethodType from ...molecule import Molecule @@ -51,13 +50,12 @@ logger = logging.getLogger(__name__) -try: +if _optionals.HAS_PYQUANTE2: + # pylint: disable=import-error from pyquante2 import molecule as pyquante_molecule from pyquante2 import rhf, uhf, rohf, basisset, onee_integrals from pyquante2.geo.zmatrix import z2xyz from pyquante2.ints.integrals import twoe_integrals -except ImportError: - logger.info("PyQuante2 is not installed. See https://github.com/rpmuller/pyquante2") class BasisType(Enum): @@ -84,6 +82,7 @@ def type_from_string(basis: str) -> "BasisType": raise QiskitNatureError(f"Invalid Basis type basis {basis}.") +@_optionals.HAS_PYQUANTE2.require_in_instance class PyQuanteDriver(ElectronicStructureDriver): """ Qiskit Nature driver using the PyQuante2 library. @@ -121,7 +120,6 @@ def __init__( """ super().__init__() validate_min("maxiters", maxiters, 1) - PyQuanteDriver.check_installed() PyQuanteDriver.check_method_supported(method) if not isinstance(atoms, str) and not isinstance(atoms, list): raise QiskitNatureError(f"Invalid atom input for PYQUANTE Driver '{atoms}'") @@ -228,6 +226,7 @@ def maxiters(self, maxiters: int) -> None: self._maxiters = maxiters @staticmethod + @_optionals.HAS_PYQUANTE2.require_in_call("PyQuanteDriver from_molecule") def from_molecule( molecule: Molecule, basis: str = "sto3g", @@ -243,11 +242,10 @@ def from_molecule( Returns: driver """ - PyQuanteDriver.check_installed() PyQuanteDriver.check_method_supported(method) kwargs = {} if driver_kwargs: - args = inspect.getfullargspec(PyQuanteDriver.__init__).args + args = inspect.signature(PyQuanteDriver.__init__).parameters.keys() for key, value in driver_kwargs.items(): if key not in ["self"] and key in args: kwargs[key] = value @@ -273,32 +271,6 @@ def to_driver_basis(basis: str) -> BasisType: """ return BasisType.type_from_string(basis) - @staticmethod - def check_installed() -> None: - """ - Checks if PyQuante is installed and available - - Raises: - MissingOptionalLibraryError: if not installed. - """ - try: - spec = importlib.util.find_spec("pyquante2") - if spec is not None: - return - except Exception as ex: - logger.debug("PyQuante2 check error %s", str(ex)) - raise MissingOptionalLibraryError( - libname="PyQuante2", - name="PyQuanteDriver", - msg="See https://github.com/rpmuller/pyquante2", - ) from ex - - raise MissingOptionalLibraryError( - libname="PyQuante2", - name="PyQuanteDriver", - msg="See https://github.com/rpmuller/pyquante2", - ) - @staticmethod def check_method_supported(method: MethodType) -> None: """ diff --git a/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py b/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py index 23eed32640..b06a83a90e 100644 --- a/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py +++ b/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py @@ -12,7 +12,6 @@ """The PySCF Driver.""" -import importlib import inspect import logging import os @@ -23,7 +22,6 @@ import numpy as np from qiskit.utils.validation import validate_min -from qiskit.exceptions import MissingOptionalLibraryError from qiskit_nature.properties.second_quantization.driver_metadata import DriverMetadata from qiskit_nature.properties.second_quantization.electronic import ( ElectronicStructureDriverResult, @@ -42,6 +40,7 @@ OneBodyElectronicIntegrals, TwoBodyElectronicIntegrals, ) +import qiskit_nature.optionals as _optionals from ....exceptions import QiskitNatureError from ..electronic_structure_driver import ElectronicStructureDriver, MethodType @@ -50,7 +49,8 @@ logger = logging.getLogger(__name__) -try: +if _optionals.HAS_PYSCF: + # pylint: disable=import-error from pyscf import __version__ as pyscf_version from pyscf import dft, gto, scf from pyscf.lib import chkfile as lib_chkfile @@ -59,8 +59,6 @@ from pyscf.tools import dump_mat warnings.filterwarnings("ignore", category=DeprecationWarning, module="pyscf") -except ImportError: - logger.info("PySCF is not installed. See https://pyscf.org/install.html") class InitialGuess(Enum): @@ -72,6 +70,7 @@ class InitialGuess(Enum): ATOM = "atom" +@_optionals.HAS_PYSCF.require_in_instance class PySCFDriver(ElectronicStructureDriver): """A Second-Quantization driver for Qiskit Nature using the PySCF library. @@ -144,7 +143,6 @@ def __init__( """ super().__init__() # First, ensure that PySCF is actually installed - PySCFDriver.check_installed() PySCFDriver.check_method_supported(method) if isinstance(atom, list): @@ -313,6 +311,7 @@ def chkfile(self, chkfile: str) -> None: self._chkfile = chkfile @staticmethod + @_optionals.HAS_PYSCF.require_in_call("PySCFDriver from_molecule") def from_molecule( molecule: Molecule, basis: str = "sto3g", @@ -328,11 +327,10 @@ def from_molecule( Returns: driver """ - PySCFDriver.check_installed() PySCFDriver.check_method_supported(method) kwargs = {} if driver_kwargs: - args = inspect.getfullargspec(PySCFDriver.__init__).args + args = inspect.signature(PySCFDriver.__init__).parameters.keys() for key, value in driver_kwargs.items(): if key not in ["self"] and key in args: kwargs[key] = value @@ -356,33 +354,6 @@ def to_driver_basis(basis: str) -> str: """ return basis - @staticmethod - def check_installed() -> None: - """Checks that PySCF is actually installed. - - Raises: - MissingOptionalLibraryError: If PySCF is not installed. - """ - try: - spec = importlib.util.find_spec("pyscf") - if spec is not None: - return - except Exception as ex: - logger.debug("PySCF check error %s", str(ex)) - raise MissingOptionalLibraryError( - libname="PySCF", - name="PySCFDriver", - pip_install="pip install 'qiskit-nature[pyscf]'", - msg="See https://pyscf.org/install.html", - ) from ex - - raise MissingOptionalLibraryError( - libname="PySCF", - name="PySCFDriver", - pip_install="pip install 'qiskit-nature[pyscf]'", - msg="See https://pyscf.org/install.html", - ) - @staticmethod def check_method_supported(method: MethodType) -> None: """ diff --git a/qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py b/qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py index 7654aa90d8..420b3962f5 100644 --- a/qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.py +++ b/qiskit_nature/drivers/second_quantization/vibrational_structure_molecule_driver.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 @@ -73,7 +73,8 @@ def driver_class_from_type( raise MissingOptionalLibraryError( libname=driver_type, name="VibrationalStructureDriverType" ) - driver_class.check_installed() + # instantiating the object will check if the driver is installed + _ = driver_class() logger.debug("%s found from type %s.", driver_class.__name__, driver_type.value) return driver_class diff --git a/qiskit_nature/optionals.py b/qiskit_nature/optionals.py new file mode 100644 index 0000000000..a400979c04 --- /dev/null +++ b/qiskit_nature/optionals.py @@ -0,0 +1,90 @@ +# 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 typing import Optional, Callable +from shutil import which +from qiskit.utils import LazyDependencyManager, LazyImportTester, LazySubprocessTester + + +class NatureLazyCommandTester(LazyDependencyManager): + """ + A lazy checker that a command-line tool is available. This just checks if the command is + not empty. + """ + + __slots__ = ("_command",) + + def __init__( + self, + command: str, + *, + name: Optional[str] = None, + callback: Optional[Callable[[bool], None]] = None, + install: Optional[str] = None, + msg: Optional[str] = None, + ): + """ + Args: + command: the string that make up the command. For example,``"g16"``. + """ + self._command = "" if command is None else command + super().__init__(name=name or self._command, callback=callback, install=install, msg=msg) + + def _is_available(self): + return bool(self._command) + + +HAS_PYQUANTE2 = LazyImportTester( + { + "pyquante2": ("molecule", "rhf", "uhf", "rohf", "basisset", "onee_integrals"), + "pyquante2.geo.zmatrix": ("z2xyz",), + "pyquante2.ints.integrals": ("twoe_integrals",), + }, + name="pyquante2", + msg="See https://github.com/rpmuller/pyquante2", +) + +HAS_PYSCF = LazyImportTester( + { + "pyscf": ("__version__", "gto", "scf"), + "pyscf.lib": ("chkfile", "logger", "param"), + "pyscf.tools": ("dump_mat",), + }, + name="pyscf", + msg="See https://pyscf.org/install.html", +) + +GAUSSIAN_16 = "g16" +GAUSSIAN_16_DESC = "Gaussian 16" +G16PROG = which(GAUSSIAN_16) + +HAS_GAUSSIAN = NatureLazyCommandTester( + G16PROG, + name=GAUSSIAN_16_DESC, + msg="Please check that it is installed correctly", +) + +PSI4 = "psi4" +PSI4_DESC = "PSI4" +PSI4PROG = which(PSI4) +if PSI4PROG is None: + PSI4PROG = PSI4 + +HAS_PSI4 = LazySubprocessTester( + (PSI4PROG, "--version"), + name=PSI4_DESC, + msg="See https://psicode.org", +) diff --git a/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py b/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py index c17c126411..f6707998fc 100644 --- a/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py +++ b/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py @@ -22,10 +22,9 @@ from retworkx import NodeIndices, PyGraph, WeightedEdgeList, adjacency_matrix from retworkx.visualization import mpl_draw -from qiskit.exceptions import MissingOptionalLibraryError -from qiskit.tools.visualization import HAS_MATPLOTLIB +from qiskit.utils import optionals as _optionals -if HAS_MATPLOTLIB: +if _optionals.HAS_MATPLOTLIB: # pylint: disable=unused-import from matplotlib.axes import Axes from matplotlib.colors import Colormap @@ -203,6 +202,7 @@ def to_adjacency_matrix(self, weighted: bool = False) -> np.ndarray: return ad_mat @staticmethod + @_optionals.HAS_MATPLOTLIB.require_in_call("Lattice _mpl") def _mpl(graph: PyGraph, self_loop: bool, **kwargs): """ Auxiliary function for drawing the lattice using matplotlib. @@ -215,10 +215,6 @@ def _mpl(graph: PyGraph, self_loop: bool, **kwargs): Raises: MissingOptionalLibraryError: Requires matplotlib. """ - if not HAS_MATPLOTLIB: - raise MissingOptionalLibraryError( - libname="Matplotlib", name="_mpl", pip_install="pip install matplotlib" - ) from matplotlib import pyplot as plt if not self_loop: diff --git a/test/__init__.py b/test/__init__.py index 4aa94f98a0..bc54aab9f8 100644 --- 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 @@ """ Qiskit Nature test packages """ -from .nature_test_case import QiskitNatureTestCase, requires_extra_library +from .nature_test_case import QiskitNatureTestCase -__all__ = ["QiskitNatureTestCase", "requires_extra_library"] +__all__ = ["QiskitNatureTestCase"] diff --git a/test/algorithms/excited_state_solvers/eigensolver_factories/test_numpy_eigensolver_factory.py b/test/algorithms/excited_state_solvers/eigensolver_factories/test_numpy_eigensolver_factory.py index 00ad193f11..e99b09b4ed 100644 --- a/test/algorithms/excited_state_solvers/eigensolver_factories/test_numpy_eigensolver_factory.py +++ b/test/algorithms/excited_state_solvers/eigensolver_factories/test_numpy_eigensolver_factory.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,7 @@ # that they have been altered from the originals. """ Test NumPyMinimumEigensovler Factory """ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase import numpy as np from qiskit.algorithms import NumPyEigensolver @@ -19,6 +19,7 @@ from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import PySCFDriver from qiskit_nature.problems.second_quantization import ElectronicStructureProblem +import qiskit_nature.optionals as _optionals class TestNumPyEigensolverFactory(QiskitNatureTestCase): @@ -27,7 +28,7 @@ class TestNumPyEigensolverFactory(QiskitNatureTestCase): # NOTE: The actual usage of this class is mostly tested in combination with the ground-state # eigensolvers (one module above). - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() diff --git a/test/algorithms/excited_state_solvers/test_excited_states_solvers.py b/test/algorithms/excited_state_solvers/test_excited_states_solvers.py index 90424b4cd4..6c627c3bd3 100644 --- a/test/algorithms/excited_state_solvers/test_excited_states_solvers.py +++ b/test/algorithms/excited_state_solvers/test_excited_states_solvers.py @@ -13,7 +13,7 @@ """ Test Numerical qEOM excited states calculation """ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase import numpy as np from qiskit import BasicAer from qiskit.utils import algorithm_globals, QuantumInstance @@ -35,12 +35,13 @@ ExcitedStatesEigensolver, QEOM, ) +import qiskit_nature.optionals as _optionals class TestNumericalQEOMESCCalculation(QiskitNatureTestCase): """Test Numerical qEOM excited states calculation""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() algorithm_globals.random_seed = 8 diff --git a/test/algorithms/ground_state_solvers/test_adapt_vqe.py b/test/algorithms/ground_state_solvers/test_adapt_vqe.py index eb9f78ec45..d98fafbfa3 100644 --- a/test/algorithms/ground_state_solvers/test_adapt_vqe.py +++ b/test/algorithms/ground_state_solvers/test_adapt_vqe.py @@ -18,7 +18,7 @@ from typing import cast -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase import numpy as np @@ -42,12 +42,13 @@ OneBodyElectronicIntegrals, TwoBodyElectronicIntegrals, ) +import qiskit_nature.optionals as _optionals class TestAdaptVQE(QiskitNatureTestCase): """Test Adaptive VQE Ground State Calculation""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() diff --git a/test/algorithms/ground_state_solvers/test_advanced_ucc_variants.py b/test/algorithms/ground_state_solvers/test_advanced_ucc_variants.py index c011726134..364b05f265 100644 --- a/test/algorithms/ground_state_solvers/test_advanced_ucc_variants.py +++ b/test/algorithms/ground_state_solvers/test_advanced_ucc_variants.py @@ -14,7 +14,7 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from qiskit import BasicAer from qiskit.utils import QuantumInstance @@ -29,6 +29,7 @@ from qiskit_nature.converters.second_quantization import QubitConverter from qiskit_nature.problems.second_quantization import ElectronicStructureProblem from qiskit_nature.transformers.second_quantization.electronic import FreezeCoreTransformer +import qiskit_nature.optionals as _optionals # pylint: disable=invalid-name @@ -37,7 +38,7 @@ class TestUCCSDHartreeFock(QiskitNatureTestCase): """Test for these extensions.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() self.driver = PySCFDriver(atom="H 0 0 0.735; H 0 0 0", basis="631g") diff --git a/test/algorithms/pes_samplers/test_bopes_sampler.py b/test/algorithms/pes_samplers/test_bopes_sampler.py index f8b8223ac4..8d8ddb45c1 100644 --- a/test/algorithms/pes_samplers/test_bopes_sampler.py +++ b/test/algorithms/pes_samplers/test_bopes_sampler.py @@ -15,7 +15,7 @@ import unittest from functools import partial -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase import numpy as np @@ -33,6 +33,7 @@ from qiskit_nature.mappers.second_quantization import ParityMapper, JordanWignerMapper from qiskit_nature.converters.second_quantization import QubitConverter from qiskit_nature.problems.second_quantization import ElectronicStructureProblem +import qiskit_nature.optionals as _optionals class TestBOPES(QiskitNatureTestCase): @@ -43,7 +44,7 @@ def setUp(self) -> None: self.seed = 50 algorithm_globals.random_seed = self.seed - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_h2_bopes_sampler(self): """Test BOPES Sampler on H2""" # Molecule @@ -79,7 +80,7 @@ def test_h2_bopes_sampler(self): energies, [-1.13618945, -1.10115033, -1.03518627], decimal=2 ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_potential_interface(self): """Tests potential interface.""" stretch = partial(Molecule.absolute_distance, atom_pair=(1, 0)) @@ -117,7 +118,7 @@ def test_potential_interface(self): np.testing.assert_array_almost_equal([pot.alpha, pot.r_0], [2.235, 0.720], decimal=3) np.testing.assert_array_almost_equal([pot.d_e, pot.m_shift], [0.2107, -1.1419], decimal=3) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_vqe_bootstrap(self): """Test with VQE and bootstrapping.""" qubit_converter = QubitConverter(JordanWignerMapper()) diff --git a/test/drivers/second_quantization/gaussiand/test_driver_gaussian.py b/test/drivers/second_quantization/gaussiand/test_driver_gaussian.py index 4bfefbb0ca..a7fd5506c6 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_gaussian.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_gaussian.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 @@ -14,19 +14,20 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from test.drivers.second_quantization.test_driver import TestDriver from qiskit_nature.drivers.second_quantization import ( GaussianDriver, ElectronicStructureDriverType, ElectronicStructureMoleculeDriver, ) +import qiskit_nature.optionals as _optionals class TestDriverGaussian(QiskitNatureTestCase, TestDriver): """Gaussian Driver tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def setUp(self): super().setUp() driver = GaussianDriver( @@ -47,7 +48,7 @@ def setUp(self): class TestDriverGaussianMolecule(QiskitNatureTestCase, TestDriver): """Gaussian Driver tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def setUp(self): super().setUp() driver = ElectronicStructureMoleculeDriver( diff --git a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_extra.py b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_extra.py index c6a4f751e0..b960817da1 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_extra.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_extra.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 @@ -18,34 +18,16 @@ from qiskit_nature.drivers.second_quantization import GaussianDriver -# We need to have an instance so we can test function but constructor calls -# an internal method to check G16 installed. We need to replace that with -# the following dummy for things to work and we do it for each test so the -# class ends up as it was -def _check_installed(): - pass - - class TestDriverGaussianExtra(QiskitNatureTestCase): """Gaussian Driver extra tests for driver specifics, errors etc""" - def setUp(self): - super().setUp() - self.good_check = GaussianDriver.check_installed - GaussianDriver.check_installed = _check_installed - # We can now create a driver without the installed (check valid) test failing - - def tearDown(self): - GaussianDriver.check_installed = self.good_check - def test_cfg_augment(self): """test input configuration augmentation""" cfg = ( "# rhf/sto-3g scf(conventional)\n\n" "h2 molecule\n\n0 1\nH 0.0 0.0 0.0\nH 0.0 0.0 0.735\n\n" ) - g16 = GaussianDriver(cfg) - aug_cfg = g16._augment_config("mymatfile.mat", cfg) + aug_cfg = GaussianDriver._augment_config("mymatfile.mat", cfg) expected = ( "# rhf/sto-3g scf(conventional)\n" "# Window=Full Int=NoRaff Symm=(NoInt,None)" diff --git a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_forces.py b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_forces.py index 1284bafe7c..18616c59c0 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_forces.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_forces.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 @@ -16,7 +16,7 @@ import warnings from typing import cast -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from qiskit_nature.drivers import Molecule, WatsonHamiltonian from qiskit_nature.drivers.second_quantization import ( @@ -25,6 +25,7 @@ VibrationalStructureDriverType, ) from qiskit_nature.properties.second_quantization.vibrational import VibrationalEnergy +import qiskit_nature.optionals as _optionals class TestDriverGaussianForces(QiskitNatureTestCase): @@ -85,7 +86,7 @@ class TestDriverGaussianForces(QiskitNatureTestCase): [2.2973803125, 3, 3, 3, 3], ] - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def test_driver_jcf(self): """Test the driver works with job control file""" driver = GaussianForcesDriver( @@ -105,7 +106,7 @@ def test_driver_jcf(self): result = driver.run() self._check_driver_result(TestDriverGaussianForces._JFC_MOLECULE_EXPECTED, result) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def test_driver_molecule(self): """Test the driver works with Molecule""" molecule = Molecule( diff --git a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_from_mat.py b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_from_mat.py index 30904fd040..47f6889e18 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_from_mat.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_from_mat.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 @@ -21,38 +21,21 @@ from qiskit_nature.properties.second_quantization.electronic import ElectronicStructureDriverResult -# We need to have an instance so we can test function but constructor calls -# an internal method to check G16 installed. We need to replace that with -# the following dummy for things to work and we do it for each test so the -# class ends up as it was -def _check_installed(): - pass - - class TestDriverGaussianFromMat(QiskitNatureTestCase, TestDriver): """Gaussian Driver tests using a saved output matrix file.""" def setUp(self): super().setUp() - self.good_check = GaussianDriver.check_installed - GaussianDriver.check_installed = _check_installed - # We can now create a driver without the installed (check valid) test failing - # and create a qmolecule from the saved output matrix file. This will test the - # parsing of it into the qmolecule is correct. - g16 = GaussianDriver() matfile = self.get_resource_path( "test_driver_gaussian_from_mat.mat", "drivers/second_quantization/gaussiand" ) try: - q_mol = g16._parse_matrix_file(matfile) + q_mol = GaussianDriver._parse_matrix_file(matfile) self.driver_result = ElectronicStructureDriverResult.from_legacy_driver_result(q_mol) except QiskitNatureError: self.tearDown() self.skipTest("GAUSSIAN qcmatrixio not found") - def tearDown(self): - GaussianDriver.check_installed = self.good_check - if __name__ == "__main__": unittest.main() diff --git a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_log.py b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_log.py index 19ab49a834..52d33eafa0 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_gaussian_log.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_gaussian_log.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 @@ -14,9 +14,10 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from qiskit_nature.drivers.second_quantization import GaussianLogDriver, GaussianLogResult +import qiskit_nature.optionals as _optionals class TestDriverGaussianLog(QiskitNatureTestCase): @@ -28,7 +29,7 @@ def setUp(self): "test_driver_gaussian_log.txt", "drivers/second_quantization/gaussiand" ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def test_log_driver(self): """Test the driver itself creates log and we can get a result""" driver = GaussianLogDriver( diff --git a/test/drivers/second_quantization/gaussiand/test_driver_methods_gaussian.py b/test/drivers/second_quantization/gaussiand/test_driver_methods_gaussian.py index 91fbe56ae9..e8b6e23d09 100644 --- a/test/drivers/second_quantization/gaussiand/test_driver_methods_gaussian.py +++ b/test/drivers/second_quantization/gaussiand/test_driver_methods_gaussian.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -14,10 +14,10 @@ import unittest -from test import requires_extra_library from test.drivers.second_quantization.test_driver_methods_gsc import TestDriverMethods from qiskit_nature.drivers.second_quantization import GaussianDriver from qiskit_nature.transformers.second_quantization.electronic import FreezeCoreTransformer +import qiskit_nature.optionals as _optionals class TestDriverMethodsGaussian(TestDriverMethods): @@ -45,7 +45,7 @@ class TestDriverMethodsGaussian(TestDriverMethods): """ - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_GAUSSIAN, "gaussian not available.") def setUp(self): super().setUp() GaussianDriver(config=self.g16_lih_config.format("rhf")) diff --git a/test/drivers/second_quantization/psi4d/test_driver_methods_psi4.py b/test/drivers/second_quantization/psi4d/test_driver_methods_psi4.py index 5d853d1bdb..7fee07e8f6 100644 --- a/test/drivers/second_quantization/psi4d/test_driver_methods_psi4.py +++ b/test/drivers/second_quantization/psi4d/test_driver_methods_psi4.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -14,10 +14,10 @@ import unittest -from test import requires_extra_library from test.drivers.second_quantization.test_driver_methods_gsc import TestDriverMethods from qiskit_nature.drivers.second_quantization import PSI4Driver from qiskit_nature.transformers.second_quantization.electronic import FreezeCoreTransformer +import qiskit_nature.optionals as _optionals class TestDriverMethodsPSI4(TestDriverMethods): @@ -51,7 +51,7 @@ class TestDriverMethodsPSI4(TestDriverMethods): }} """ - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PSI4, "psi4 not available.") def setUp(self): super().setUp() PSI4Driver(config=self.psi4_lih_config.format("rhf")) diff --git a/test/drivers/second_quantization/psi4d/test_driver_psi4.py b/test/drivers/second_quantization/psi4d/test_driver_psi4.py index 5e64409415..eb1c7d0a06 100644 --- a/test/drivers/second_quantization/psi4d/test_driver_psi4.py +++ b/test/drivers/second_quantization/psi4d/test_driver_psi4.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 @@ -14,19 +14,20 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from test.drivers.second_quantization.test_driver import TestDriver from qiskit_nature.drivers.second_quantization import ( PSI4Driver, ElectronicStructureDriverType, ElectronicStructureMoleculeDriver, ) +import qiskit_nature.optionals as _optionals class TestDriverPSI4(QiskitNatureTestCase, TestDriver): """PSI4 Driver tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PSI4, "psi4 not available.") def setUp(self): super().setUp() driver = PSI4Driver( @@ -51,7 +52,7 @@ def setUp(self): class TestDriverPSI4Molecule(QiskitNatureTestCase, TestDriver): """PSI4 Driver molecule tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PSI4, "psi4 not available.") def setUp(self): super().setUp() driver = ElectronicStructureMoleculeDriver( diff --git a/test/drivers/second_quantization/psi4d/test_driver_psi4_extra.py b/test/drivers/second_quantization/psi4d/test_driver_psi4_extra.py index ac344a09d8..f4a598d74c 100644 --- a/test/drivers/second_quantization/psi4d/test_driver_psi4_extra.py +++ b/test/drivers/second_quantization/psi4d/test_driver_psi4_extra.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 @@ -16,19 +16,19 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from qiskit_nature.drivers.second_quantization import PSI4Driver from qiskit_nature import QiskitNatureError from qiskit_nature.properties.second_quantization.electronic import ElectronicEnergy +import qiskit_nature.optionals as _optionals class TestDriverPSI4Extra(QiskitNatureTestCase): """PSI4 Driver extra tests for driver specifics, errors etc""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PSI4, "psi4 not available.") def setUp(self): super().setUp() - PSI4Driver.check_installed() def test_input_format_list(self): """input as a list""" diff --git a/test/drivers/second_quantization/pyquanted/test_driver_methods_pyquante.py b/test/drivers/second_quantization/pyquanted/test_driver_methods_pyquante.py index 117421c638..9fe501f7ef 100644 --- a/test/drivers/second_quantization/pyquanted/test_driver_methods_pyquante.py +++ b/test/drivers/second_quantization/pyquanted/test_driver_methods_pyquante.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -14,16 +14,16 @@ import unittest -from test import requires_extra_library from test.drivers.second_quantization.test_driver_methods_gsc import TestDriverMethods from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import PyQuanteDriver, BasisType, MethodType +import qiskit_nature.optionals as _optionals class TestDriverMethodsPyquante(TestDriverMethods): """Driver Methods Pyquante tests""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYQUANTE2, "pyquante2 not available.") def setUp(self): super().setUp() PyQuanteDriver(atoms=self.lih) diff --git a/test/drivers/second_quantization/pyquanted/test_driver_pyquante.py b/test/drivers/second_quantization/pyquanted/test_driver_pyquante.py index 3d3401e37f..b51ac8bff3 100644 --- a/test/drivers/second_quantization/pyquanted/test_driver_pyquante.py +++ b/test/drivers/second_quantization/pyquanted/test_driver_pyquante.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,7 +13,7 @@ """ Test Driver PyQuante """ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from test.drivers.second_quantization.test_driver import TestDriver from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import ( @@ -22,12 +22,13 @@ ElectronicStructureDriverType, ElectronicStructureMoleculeDriver, ) +import qiskit_nature.optionals as _optionals class TestDriverPyQuante(QiskitNatureTestCase, TestDriver): """PYQUANTE Driver tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYQUANTE2, "pyquante2 not available.") def setUp(self): super().setUp() driver = PyQuanteDriver( @@ -43,7 +44,7 @@ def setUp(self): class TestDriverPyQuanteMolecule(QiskitNatureTestCase, TestDriver): """PYQUANTE Driver molecule tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYQUANTE2, "pyquante2 not available.") def setUp(self): super().setUp() driver = ElectronicStructureMoleculeDriver( diff --git a/test/drivers/second_quantization/pyscfd/test_driver_methods_pyscf.py b/test/drivers/second_quantization/pyscfd/test_driver_methods_pyscf.py index 3ba1c765c5..3d06074ade 100644 --- a/test/drivers/second_quantization/pyscfd/test_driver_methods_pyscf.py +++ b/test/drivers/second_quantization/pyscfd/test_driver_methods_pyscf.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -14,19 +14,19 @@ import unittest -from test import requires_extra_library from test.drivers.second_quantization.test_driver_methods_gsc import TestDriverMethods from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import PySCFDriver, MethodType from qiskit_nature.mappers.second_quantization import BravyiKitaevMapper, ParityMapper from qiskit_nature.converters.second_quantization.qubit_converter import QubitConverter from qiskit_nature.transformers.second_quantization.electronic import FreezeCoreTransformer +import qiskit_nature.optionals as _optionals class TestDriverMethodsPySCF(TestDriverMethods): """Driver Methods PySCF tests""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() PySCFDriver(atom=self.lih) diff --git a/test/drivers/second_quantization/pyscfd/test_driver_pyscf.py b/test/drivers/second_quantization/pyscfd/test_driver_pyscf.py index a15e21e8a8..8affb63479 100644 --- a/test/drivers/second_quantization/pyscfd/test_driver_pyscf.py +++ b/test/drivers/second_quantization/pyscfd/test_driver_pyscf.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 @@ -15,7 +15,7 @@ from typing import cast import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from test.drivers.second_quantization.test_driver import TestDriver from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import ( @@ -25,12 +25,13 @@ ) from qiskit_nature import QiskitNatureError from qiskit_nature.properties.second_quantization.electronic import ElectronicEnergy +import qiskit_nature.optionals as _optionals class TestDriverPySCF(QiskitNatureTestCase, TestDriver): """PYSCF Driver tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() driver = PySCFDriver( @@ -83,7 +84,7 @@ def test_zmatrix(self): class TestDriverPySCFMolecule(QiskitNatureTestCase, TestDriver): """PYSCF Driver Molecule tests.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() driver = ElectronicStructureMoleculeDriver( diff --git a/test/drivers/second_quantization/test_molecule_driver.py b/test/drivers/second_quantization/test_molecule_driver.py index e525e4c4e9..e0e19f8ec4 100644 --- a/test/drivers/second_quantization/test_molecule_driver.py +++ b/test/drivers/second_quantization/test_molecule_driver.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 @@ -15,8 +15,9 @@ from typing import cast import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase from ddt import ddt, data +from qiskit.exceptions import MissingOptionalLibraryError from qiskit_nature.drivers.second_quantization import ( MethodType, ElectronicStructureDriverType, @@ -27,6 +28,7 @@ from qiskit_nature.drivers import Molecule from qiskit_nature.exceptions import UnsupportMethodError from qiskit_nature.properties.second_quantization.electronic import ElectronicEnergy +import qiskit_nature.optionals as _optionals @ddt @@ -41,7 +43,7 @@ def setUp(self): charge=0, ) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_invalid_kwarg(self): """test invalid kwarg""" driver = ElectronicStructureMoleculeDriver( @@ -60,14 +62,16 @@ def test_invalid_kwarg(self): (ElectronicStructureDriverType.PYQUANTE, -1.1169989925292956), (ElectronicStructureDriverType.GAUSSIAN, -1.1169989967389082), ) - @requires_extra_library def test_driver(self, config): """test driver""" driver_type, hf_energy = config driver = ElectronicStructureMoleculeDriver( self._molecule, basis="sto3g", driver_type=driver_type ) - driver_result = driver.run() + try: + driver_result = driver.run() + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) electronic_energy = cast(ElectronicEnergy, driver_result.get_property(ElectronicEnergy)) self.assertAlmostEqual(electronic_energy.reference_energy, hf_energy, places=5) @@ -76,7 +80,6 @@ def test_driver(self, config): ElectronicStructureDriverType.PYQUANTE, ElectronicStructureDriverType.GAUSSIAN, ) - @requires_extra_library def test_unsupported_method(self, driver_type): """test unsupported methods""" for method in [MethodType.RKS, MethodType.ROKS, MethodType.UKS]: @@ -84,7 +87,10 @@ def test_unsupported_method(self, driver_type): self._molecule, basis="sto3g", method=method, driver_type=driver_type ) with self.assertRaises(UnsupportMethodError): - _ = driver.run() + try: + _ = driver.run() + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) @ddt @@ -140,13 +146,15 @@ def _check_driver_result(self, expected, watson): VibrationalStructureDriverType.AUTO, VibrationalStructureDriverType.GAUSSIAN_FORCES, ) - @requires_extra_library def test_driver(self, driver_type): """test driver""" driver = VibrationalStructureMoleculeDriver( self._molecule, basis="6-31g", driver_type=driver_type ) - result = driver.run() + try: + result = driver.run() + except MissingOptionalLibraryError as ex: + self.skipTest(str(ex)) self._check_driver_result(TestVibrationalStructureMoleculeDriver._MOLECULE_EXPECTED, result) diff --git a/test/nature_test_case.py b/test/nature_test_case.py index 9ce0c2847e..b40a315293 100644 --- a/test/nature_test_case.py +++ b/test/nature_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 QiskitNatureTestCase(unittest.TestCase, ABC): """Nature Test Case""" diff --git a/test/problems/second_quantization/electronic/builders/test_hopping_ops_builder.py b/test/problems/second_quantization/electronic/builders/test_hopping_ops_builder.py index c58c0cbceb..5cabde1ab6 100644 --- a/test/problems/second_quantization/electronic/builders/test_hopping_ops_builder.py +++ b/test/problems/second_quantization/electronic/builders/test_hopping_ops_builder.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,8 @@ # that they have been altered from the originals. """Tests Hopping Operators builder.""" -from test import QiskitNatureTestCase, requires_extra_library +import unittest +from test import QiskitNatureTestCase from qiskit.opflow import PauliSumOp from qiskit.utils import algorithm_globals @@ -24,12 +25,13 @@ from qiskit_nature.problems.second_quantization.electronic.builders.hopping_ops_builder import ( _build_qeom_hopping_ops, ) +import qiskit_nature.optionals as _optionals class TestHoppingOpsBuilder(QiskitNatureTestCase): """Tests Hopping Operators builder.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() algorithm_globals.random_seed = 8 diff --git a/test/problems/second_quantization/electronic/test_electronic_structure_problem.py b/test/problems/second_quantization/electronic/test_electronic_structure_problem.py index 680bdb5bbf..caba59b7f4 100644 --- a/test/problems/second_quantization/electronic/test_electronic_structure_problem.py +++ b/test/problems/second_quantization/electronic/test_electronic_structure_problem.py @@ -11,7 +11,8 @@ # that they have been altered from the originals. """Tests Electronic Structure Problem.""" -from test import QiskitNatureTestCase, requires_extra_library +import unittest +from test import QiskitNatureTestCase from test.problems.second_quantization.electronic.resources.resource_reader import ( read_expected_file, ) @@ -34,6 +35,7 @@ ActiveSpaceTransformer, FreezeCoreTransformer, ) +import qiskit_nature.optionals as _optionals class TestElectronicStructureProblem(QiskitNatureTestCase): @@ -125,7 +127,7 @@ def test_second_q_ops_with_active_space(self): class TestElectronicStructureProblemLegacyDrivers(QiskitNatureTestCase): """Tests Electronic Structure Problem.""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_sector_locator_h2o(self): """Test sector locator.""" driver = PySCFDriver( @@ -143,7 +145,7 @@ def test_sector_locator_h2o(self): ) self.assertListEqual(qubit_conv.z2symmetries.tapering_values, [1, -1]) - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def test_sector_locator_homonuclear(self): """Test sector locator.""" molecule = Molecule( diff --git a/test/test_readme_sample.py b/test/test_readme_sample.py index caf608f19c..b789cd8cd9 100644 --- a/test/test_readme_sample.py +++ b/test/test_readme_sample.py @@ -18,13 +18,14 @@ import unittest -from test import QiskitNatureTestCase, requires_extra_library +from test import QiskitNatureTestCase +import qiskit_nature.optionals as _optionals class TestReadmeSample(QiskitNatureTestCase): """Test sample code from readme""" - @requires_extra_library + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() # pylint: disable=import-outside-toplevel From 03b574347c12e3ac90b9ad8f407905e2a4cb27d3 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 4 Feb 2022 10:34:53 -0500 Subject: [PATCH 2/4] Migrate most ImportError to Lazy framework --- .../test_bosonic_esc_calculation.py | 12 +++--- .../test_groundstate_eigensolver.py | 39 +++++-------------- .../ground_state_solvers/test_swaprz.py | 4 +- .../pes_samplers/test_bopes_sampler.py | 7 ++-- .../fcidumpd/test_driver_fcidump_dumper.py | 12 +++--- test/test_readme_sample.py | 18 +-------- 6 files changed, 31 insertions(+), 61 deletions(-) diff --git a/test/algorithms/excited_state_solvers/test_bosonic_esc_calculation.py b/test/algorithms/excited_state_solvers/test_bosonic_esc_calculation.py index 1289ea9e4f..310e4596cf 100644 --- a/test/algorithms/excited_state_solvers/test_bosonic_esc_calculation.py +++ b/test/algorithms/excited_state_solvers/test_bosonic_esc_calculation.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 @@ -19,8 +19,8 @@ from test import QiskitNatureTestCase -from qiskit import Aer -from qiskit.utils import algorithm_globals, QuantumInstance +import qiskit +from qiskit.utils import algorithm_globals, QuantumInstance, optionals from qiskit.algorithms.optimizers import COBYLA from qiskit_nature.drivers import WatsonHamiltonian @@ -109,11 +109,12 @@ def test_numpy_factory(self): for idx, energy in enumerate(self.reference_energies): self.assertAlmostEqual(results.computed_vibrational_energies[idx], energy, places=4) + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_vqe_uvccsd_factory(self): """Test with VQE plus UVCCSD""" optimizer = COBYLA(maxiter=5000) solver = VQEUVCCFactory( - QuantumInstance(Aer.get_backend("aer_simulator_statevector")), + QuantumInstance(qiskit.Aer.get_backend("aer_simulator_statevector")), optimizer=optimizer, ) gsc = GroundStateEigensolver(self.qubit_converter, solver) @@ -122,6 +123,7 @@ def test_vqe_uvccsd_factory(self): for idx, energy in enumerate(self.reference_energies): self.assertAlmostEqual(results.computed_vibrational_energies[idx], energy, places=1) + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_vqe_uvccsd_with_callback(self): """Test VQE UVCCSD with callback.""" @@ -130,7 +132,7 @@ def cb_callback(nfev, parameters, energy, stddev): optimizer = COBYLA(maxiter=5000) solver = VQEUVCCFactory( - QuantumInstance(Aer.get_backend("aer_simulator_statevector")), + QuantumInstance(qiskit.Aer.get_backend("aer_simulator_statevector")), optimizer=optimizer, callback=cb_callback, ) diff --git a/test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py b/test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py index 32e5b4c699..ed22a57827 100644 --- a/test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py +++ b/test/algorithms/ground_state_solvers/test_groundstate_eigensolver.py @@ -22,12 +22,13 @@ import numpy as np +import qiskit from qiskit import BasicAer from qiskit.algorithms import VQE from qiskit.algorithms.optimizers import SLSQP, SPSA from qiskit.opflow import AerPauliExpectation, PauliExpectation from qiskit.test import slow_test -from qiskit.utils import QuantumInstance, algorithm_globals +from qiskit.utils import QuantumInstance, algorithm_globals, optionals from qiskit_nature import settings from qiskit_nature.algorithms import ( @@ -310,16 +311,11 @@ def test_eval_op_qasm(self): self.assertAlmostEqual(res_qasm.eigenenergies[0], mean[0].real) + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_eval_op_qasm_aer(self): """Regression tests against https://github.com/Qiskit/qiskit-nature/issues/53.""" - try: - # pylint: disable=import-outside-toplevel - from qiskit import Aer - backend = Aer.get_backend("aer_simulator") - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return + backend = qiskit.Aer.get_backend("aer_simulator") solver = VQEUCCFactory( optimizer=SLSQP(maxiter=100), @@ -398,16 +394,11 @@ def test_uccsd_hf_qasm(self): self.assertAlmostEqual(result.total_energies[0], -1.138, places=2) @slow_test + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_uccsd_hf_aer_statevector(self): """uccsd hf test with Aer statevector""" - try: - # pylint: disable=import-outside-toplevel - from qiskit import Aer - backend = Aer.get_backend("aer_simulator_statevector") - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return + backend = qiskit.Aer.get_backend("aer_simulator_statevector") ansatz = self._prepare_uccsd_hf(self.qubit_converter) @@ -424,16 +415,11 @@ def test_uccsd_hf_aer_statevector(self): self.assertAlmostEqual(result.total_energies[0], self.reference_energy, places=6) @slow_test + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_uccsd_hf_aer_qasm(self): """uccsd hf test with Aer qasm simulator.""" - try: - # pylint: disable=import-outside-toplevel - from qiskit import Aer - backend = Aer.get_backend("aer_simulator") - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return + backend = qiskit.Aer.get_backend("aer_simulator") ansatz = self._prepare_uccsd_hf(self.qubit_converter) @@ -455,16 +441,11 @@ def test_uccsd_hf_aer_qasm(self): self.assertAlmostEqual(result.total_energies[0], -1.131, places=2) @slow_test + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_uccsd_hf_aer_qasm_snapshot(self): """uccsd hf test with Aer qasm simulator snapshot.""" - try: - # pylint: disable=import-outside-toplevel - from qiskit import Aer - backend = Aer.get_backend("aer_simulator") - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return + backend = qiskit.Aer.get_backend("aer_simulator") ansatz = self._prepare_uccsd_hf(self.qubit_converter) diff --git a/test/algorithms/ground_state_solvers/test_swaprz.py b/test/algorithms/ground_state_solvers/test_swaprz.py index fb370ee5a4..d2d6d97f78 100644 --- a/test/algorithms/ground_state_solvers/test_swaprz.py +++ b/test/algorithms/ground_state_solvers/test_swaprz.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2019, 2021. +# (C) Copyright IBM 2019, 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 @@ -32,7 +32,6 @@ from qiskit_nature.properties.second_quantization.electronic import ParticleNumber -@slow_test class TestExcitationPreserving(QiskitNatureTestCase): """The ExcitationPresering wavefunction was design to preserve the excitation of the system. @@ -47,6 +46,7 @@ def setUp(self): algorithm_globals.random_seed = self.seed self.reference_energy = -1.137305593252385 + @slow_test def test_excitation_preserving(self): """Test the excitation preserving wavefunction on a chemistry example.""" diff --git a/test/algorithms/pes_samplers/test_bopes_sampler.py b/test/algorithms/pes_samplers/test_bopes_sampler.py index 8d8ddb45c1..86a57eb98c 100644 --- a/test/algorithms/pes_samplers/test_bopes_sampler.py +++ b/test/algorithms/pes_samplers/test_bopes_sampler.py @@ -19,9 +19,9 @@ import numpy as np -from qiskit import Aer +import qiskit from qiskit.algorithms import NumPyMinimumEigensolver, VQE -from qiskit.utils import algorithm_globals, QuantumInstance +from qiskit.utils import algorithm_globals, QuantumInstance, optionals from qiskit_nature.algorithms import GroundStateEigensolver, BOPESSampler from qiskit_nature.algorithms.pes_samplers import MorsePotential @@ -119,11 +119,12 @@ def test_potential_interface(self): np.testing.assert_array_almost_equal([pot.d_e, pot.m_shift], [0.2107, -1.1419], decimal=3) @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_vqe_bootstrap(self): """Test with VQE and bootstrapping.""" qubit_converter = QubitConverter(JordanWignerMapper()) quantum_instance = QuantumInstance( - backend=Aer.get_backend("aer_simulator_statevector"), + backend=qiskit.Aer.get_backend("aer_simulator_statevector"), seed_simulator=self.seed, seed_transpiler=self.seed, ) diff --git a/test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.py b/test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.py index cd2b8c74eb..8641bd248f 100644 --- a/test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.py +++ b/test/drivers/second_quantization/fcidumpd/test_driver_fcidump_dumper.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 @@ -20,6 +20,7 @@ from qiskit_nature import QiskitNatureError from qiskit_nature.drivers import UnitsType from qiskit_nature.drivers.second_quantization import FCIDumpDriver, PySCFDriver +import qiskit_nature.optionals as _optionals class BaseTestDriverFCIDumpDumper(ABC): @@ -107,6 +108,7 @@ def test_dumped_h2(self): class TestDriverFCIDumpDumpH2(QiskitNatureTestCase, BaseTestDriverFCIDumpDumper): """RHF FCIDump Driver tests.""" + @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") def setUp(self): super().setUp() self.core_energy = 0.7199 @@ -129,14 +131,12 @@ def setUp(self): with tempfile.NamedTemporaryFile() as dump: FCIDumpDriver.dump(driver_result, dump.name) - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel,import-error from pyscf.tools import fcidump as pyscf_fcidump self.dumped = pyscf_fcidump.read(dump.name) - except QiskitNatureError: - self.skipTest("PYSCF driver does not appear to be installed.") - except ImportError: - self.skipTest("PYSCF driver does not appear to be installed.") + except QiskitNatureError as ex: + self.skipTest(str(ex)) if __name__ == "__main__": diff --git a/test/test_readme_sample.py b/test/test_readme_sample.py index b789cd8cd9..84a3d757f9 100644 --- a/test/test_readme_sample.py +++ b/test/test_readme_sample.py @@ -19,6 +19,7 @@ import unittest from test import QiskitNatureTestCase +from qiskit.utils import optionals import qiskit_nature.optionals as _optionals @@ -26,22 +27,7 @@ class TestReadmeSample(QiskitNatureTestCase): """Test sample code from readme""" @unittest.skipIf(not _optionals.HAS_PYSCF, "pyscf not available.") - def setUp(self): - super().setUp() - # pylint: disable=import-outside-toplevel - from qiskit_nature.drivers.second_quantization import PySCFDriver - - PySCFDriver(atom="Li .0 .0 .0; H .0 .0 1.6") - - try: - # pylint: disable=import-outside-toplevel - from qiskit import Aer - - _ = Aer.get_backend("aer_simulator_statevector") - except ImportError as ex: - self.skipTest(f"Aer doesn't appear to be installed. Error: '{str(ex)}'") - return - + @unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test") def test_readme_sample(self): """readme sample test""" # pylint: disable=import-outside-toplevel,redefined-builtin From 7ec8f4ad315fbf4d5e2fa05c285390b8cd43a84a Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Fri, 4 Feb 2022 20:14:45 -0500 Subject: [PATCH 3/4] Remove global optionals check from pyscf,pyquante --- .../gaussiand/gaussian_forces_driver.py | 2 +- .../gaussiand/gaussiandriver.py | 2 +- .../second_quantization/psi4d/psi4driver.py | 2 +- .../pyquanted/pyquantedriver.py | 26 +++++++++---- .../second_quantization/pyscfd/pyscfdriver.py | 38 +++++++++++++------ qiskit_nature/optionals.py | 2 - .../lattice/lattices/lattice.py | 3 +- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py index 8da7b13218..badace9792 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussian_forces_driver.py @@ -78,7 +78,7 @@ def __init__( _optionals.HAS_GAUSSIAN.require_now("GaussianForcesDriver __init__") @staticmethod - @_optionals.HAS_GAUSSIAN.require_in_call("GaussianForcesDriver from_molecule") + @_optionals.HAS_GAUSSIAN.require_in_call def from_molecule( molecule: Molecule, basis: str = "sto-3g", diff --git a/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py b/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py index 4f15c78929..ea89a56919 100644 --- a/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py +++ b/qiskit_nature/drivers/second_quantization/gaussiand/gaussiandriver.py @@ -73,7 +73,7 @@ def __init__( self._config = config @staticmethod - @_optionals.HAS_GAUSSIAN.require_in_call("GaussianDriver from_molecule") + @_optionals.HAS_GAUSSIAN.require_in_call def from_molecule( molecule: Molecule, basis: str = "sto-3g", diff --git a/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py b/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py index e4d6f54ea4..b386b7a708 100644 --- a/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py +++ b/qiskit_nature/drivers/second_quantization/psi4d/psi4driver.py @@ -66,7 +66,7 @@ def __init__( self._config = config @staticmethod - @_optionals.HAS_PSI4.require_in_call("PSI4Driver from_molecule") + @_optionals.HAS_PSI4.require_in_call def from_molecule( molecule: Molecule, basis: str = "sto3g", diff --git a/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py b/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py index bb1e4c63a3..6c81876702 100644 --- a/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py +++ b/qiskit_nature/drivers/second_quantization/pyquanted/pyquantedriver.py @@ -50,13 +50,6 @@ logger = logging.getLogger(__name__) -if _optionals.HAS_PYQUANTE2: - # pylint: disable=import-error - from pyquante2 import molecule as pyquante_molecule - from pyquante2 import rhf, uhf, rohf, basisset, onee_integrals - from pyquante2.geo.zmatrix import z2xyz - from pyquante2.ints.integrals import twoe_integrals - class BasisType(Enum): """Basis Type""" @@ -119,6 +112,10 @@ def __init__( QiskitNatureError: Invalid Input """ super().__init__() + # pylint: disable=import-error + from pyquante2 import molecule as pyquante_molecule + from pyquante2 import rhf, uhf, rohf, basisset + validate_min("maxiters", maxiters, 1) PyQuanteDriver.check_method_supported(method) if not isinstance(atoms, str) and not isinstance(atoms, list): @@ -226,7 +223,7 @@ def maxiters(self, maxiters: int) -> None: self._maxiters = maxiters @staticmethod - @_optionals.HAS_PYQUANTE2.require_in_call("PyQuanteDriver from_molecule") + @_optionals.HAS_PYQUANTE2.require_in_call def from_molecule( molecule: Molecule, basis: str = "sto3g", @@ -299,6 +296,9 @@ def run(self) -> ElectronicStructureDriverResult: return driver_result def _build_molecule(self) -> None: + # pylint: disable=import-error + from pyquante2 import molecule as pyquante_molecule + atoms = self._check_molecule_format(self.atoms) parts = [x.strip() for x in atoms.split(";")] @@ -319,6 +319,9 @@ def _build_molecule(self) -> None: @staticmethod def _check_molecule_format(val: str) -> str: """If it seems to be zmatrix rather than xyz format we convert before returning""" + # pylint: disable=import-error + from pyquante2.geo.zmatrix import z2xyz + atoms = [x.strip() for x in val.split(";")] if atoms is None or len(atoms) < 1: raise QiskitNatureError("Molecule format error: " + val) @@ -377,6 +380,9 @@ def run_pyquante(self): Raises: QiskitNatureError: If an invalid HF method type was supplied. """ + # pylint: disable=import-error + from pyquante2 import rhf, uhf, rohf, basisset + self._bfs = basisset(self._mol, self.basis.value) if self.method == MethodType.RHF: @@ -469,6 +475,10 @@ def _populate_driver_result_particle_number( def _populate_driver_result_electronic_energy( self, driver_result: ElectronicStructureDriverResult ) -> None: + # pylint: disable=import-error + from pyquante2 import onee_integrals + from pyquante2.ints.integrals import twoe_integrals + basis_transform = driver_result.get_property(ElectronicBasisTransform) integrals = onee_integrals(self._bfs, self._mol) diff --git a/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py b/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py index b06a83a90e..7b2295a028 100644 --- a/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py +++ b/qiskit_nature/drivers/second_quantization/pyscfd/pyscfdriver.py @@ -49,16 +49,7 @@ logger = logging.getLogger(__name__) -if _optionals.HAS_PYSCF: - # pylint: disable=import-error - from pyscf import __version__ as pyscf_version - from pyscf import dft, gto, scf - from pyscf.lib import chkfile as lib_chkfile - from pyscf.lib import logger as pylogger - from pyscf.lib import param - from pyscf.tools import dump_mat - - warnings.filterwarnings("ignore", category=DeprecationWarning, module="pyscf") +warnings.filterwarnings("ignore", category=DeprecationWarning, module="pyscf") class InitialGuess(Enum): @@ -142,7 +133,10 @@ def __init__( .. _7: https://pyscf.org/pyscf_api_docs/pyscf.lib.html#module-pyscf.lib.chkfile """ super().__init__() - # First, ensure that PySCF is actually installed + # pylint: disable=import-error + from pyscf import gto, scf + + # First, ensure that PySCF supports the method PySCFDriver.check_method_supported(method) if isinstance(atom, list): @@ -311,7 +305,7 @@ def chkfile(self, chkfile: str) -> None: self._chkfile = chkfile @staticmethod - @_optionals.HAS_PYSCF.require_in_call("PySCFDriver from_molecule") + @_optionals.HAS_PYSCF.require_in_call def from_molecule( molecule: Molecule, basis: str = "sto3g", @@ -391,6 +385,10 @@ def _build_molecule(self) -> None: # molecule is in PySCF atom string format e.g. "H .0 .0 .0; H .0 .0 0.2" # or in Z-Matrix format e.g. "H; O 1 1.08; H 2 1.08 1 107.5" # other parameters are as per PySCF got.Mole format + # pylint: disable=import-error + from pyscf import gto + from pyscf.lib import logger as pylogger + from pyscf.lib import param atom = self._check_molecule_format(self.atom) if self._max_memory is None: @@ -442,6 +440,9 @@ def _check_molecule_format(val: str) -> Union[str, List[str]]: Returns: The coordinates in XYZ format. """ + # pylint: disable=import-error + from pyscf import gto + atoms = [x.strip() for x in val.split(";")] if atoms is None or len(atoms) < 1: raise QiskitNatureError("Molecule format error: " + val) @@ -470,6 +471,10 @@ def run_pyscf(self) -> None: Raises: QiskitNatureError: If an invalid HF method type was supplied. """ + # pylint: disable=import-error + from pyscf import dft, scf + from pyscf.lib import chkfile as lib_chkfile + method_name = None method_cls = None try: @@ -535,6 +540,9 @@ def _populate_driver_result_molecule( def _populate_driver_result_metadata( self, driver_result: ElectronicStructureDriverResult ) -> None: + # pylint: disable=import-error + from pyscf import __version__ as pyscf_version + cfg = [ f"atom={self._atom}", f"unit={self._unit.value}", @@ -561,6 +569,9 @@ def _populate_driver_result_metadata( def _populate_driver_result_basis_transform( self, driver_result: ElectronicStructureDriverResult ) -> None: + # pylint: disable=import-error + from pyscf.tools import dump_mat + mo_coeff, mo_coeff_b = self._extract_mo_data("mo_coeff", array_dimension=3) if logger.isEnabledFor(logging.DEBUG): @@ -602,6 +613,9 @@ def _populate_driver_result_particle_number( def _populate_driver_result_electronic_energy( self, driver_result: ElectronicStructureDriverResult ) -> None: + # pylint: disable=import-error + from pyscf import gto + basis_transform = driver_result.get_property(ElectronicBasisTransform) one_body_ao = OneBodyElectronicIntegrals( diff --git a/qiskit_nature/optionals.py b/qiskit_nature/optionals.py index a400979c04..8c89af781d 100644 --- a/qiskit_nature/optionals.py +++ b/qiskit_nature/optionals.py @@ -25,8 +25,6 @@ class NatureLazyCommandTester(LazyDependencyManager): not empty. """ - __slots__ = ("_command",) - def __init__( self, command: str, diff --git a/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py b/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py index f6707998fc..50ab59cb00 100644 --- a/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py +++ b/qiskit_nature/problems/second_quantization/lattice/lattices/lattice.py @@ -202,7 +202,7 @@ def to_adjacency_matrix(self, weighted: bool = False) -> np.ndarray: return ad_mat @staticmethod - @_optionals.HAS_MATPLOTLIB.require_in_call("Lattice _mpl") + @_optionals.HAS_MATPLOTLIB.require_in_call def _mpl(graph: PyGraph, self_loop: bool, **kwargs): """ Auxiliary function for drawing the lattice using matplotlib. @@ -215,6 +215,7 @@ def _mpl(graph: PyGraph, self_loop: bool, **kwargs): Raises: MissingOptionalLibraryError: Requires matplotlib. """ + # pylint: disable=unused-import from matplotlib import pyplot as plt if not self_loop: From 1dc2f1356ae47a96363eca820e34e160b2208d59 Mon Sep 17 00:00:00 2001 From: Manoel Marques Date: Sat, 5 Feb 2022 10:01:32 -0500 Subject: [PATCH 4/4] Create NatureLazySubprocessTester for Gaussian and PSI4 --- qiskit_nature/optionals.py | 49 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/qiskit_nature/optionals.py b/qiskit_nature/optionals.py index 8c89af781d..0c5a5ae0fa 100644 --- a/qiskit_nature/optionals.py +++ b/qiskit_nature/optionals.py @@ -14,20 +14,21 @@ Additional optional constants. """ -from typing import Optional, Callable -from shutil import which -from qiskit.utils import LazyDependencyManager, LazyImportTester, LazySubprocessTester +from typing import Optional, Callable, Iterable, Union +import shutil +from qiskit.utils import LazyImportTester, LazySubprocessTester -class NatureLazyCommandTester(LazyDependencyManager): - """ - A lazy checker that a command-line tool is available. This just checks if the command is - not empty. +class NatureLazySubprocessTester(LazySubprocessTester): + """A lazy checker that a command-line tool is available. + First it checks with `shutil.which`. + Then it will run the command based on flag passed. """ def __init__( self, - command: str, + command: Union[str, Iterable[str]], + run: Optional[bool] = True, *, name: Optional[str] = None, callback: Optional[Callable[[bool], None]] = None, @@ -36,13 +37,20 @@ def __init__( ): """ Args: - command: the string that make up the command. For example,``"g16"``. + command: the strings that make up the command to be run. For example, + ``["pdflatex", "-version"]``. + run: flag to indicate if the command should be run + + Raises: + ValueError: if an empty command is given. """ - self._command = "" if command is None else command - super().__init__(name=name or self._command, callback=callback, install=install, msg=msg) + self._run = run + super().__init__(command=command, name=name, callback=callback, install=install, msg=msg) def _is_available(self): - return bool(self._command) + if shutil.which(self._command[0]) is None: + return False + return super()._is_available() if self._run else True HAS_PYQUANTE2 = LazyImportTester( @@ -67,22 +75,17 @@ def _is_available(self): GAUSSIAN_16 = "g16" GAUSSIAN_16_DESC = "Gaussian 16" -G16PROG = which(GAUSSIAN_16) - -HAS_GAUSSIAN = NatureLazyCommandTester( - G16PROG, - name=GAUSSIAN_16_DESC, +HAS_GAUSSIAN = NatureLazySubprocessTester( + GAUSSIAN_16, + False, + name=GAUSSIAN_16, msg="Please check that it is installed correctly", ) PSI4 = "psi4" PSI4_DESC = "PSI4" -PSI4PROG = which(PSI4) -if PSI4PROG is None: - PSI4PROG = PSI4 - -HAS_PSI4 = LazySubprocessTester( - (PSI4PROG, "--version"), +HAS_PSI4 = NatureLazySubprocessTester( + (PSI4, "--version"), name=PSI4_DESC, msg="See https://psicode.org", )