diff --git a/.azure/test-linux.yml b/.azure/test-linux.yml index c37730327cdf..e9c469a6a31d 100644 --- a/.azure/test-linux.yml +++ b/.azure/test-linux.yml @@ -165,3 +165,8 @@ jobs: - bash: image_tests/bin/python -m unittest discover -v test/ipynb displayName: 'Run image test' + env: + # Needed to suppress a warning in jupyter-core 5.x by eagerly migrating to + # a new internal interface that will be the default in jupyter-core 6.x. + # This variable should become redundant on release of jupyter-core 6. + JUPYTER_PLATFORM_DIRS: 1 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f0c957994ccb..1cb82d976736 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,11 +11,12 @@ jobs: name: Coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 name: Install Python with: python-version: '3.8' + - name: Install Rust toolchain uses: actions-rs/toolchain@v1 with: @@ -23,12 +24,12 @@ jobs: override: true profile: default components: llvm-tools-preview + - name: Install dependencies run: | - pip install coveragepy-lcov + cargo install grcov sudo apt-get install lcov - - name: Download grcov for rust coverage - run: curl -L https://github.com/mozilla/grcov/releases/download/v0.8.11/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar xj + - name: Build and install qiskit-terra run: pip install -e . env: @@ -36,36 +37,28 @@ jobs: RUSTFLAGS: "-Cinstrument-coverage" LLVM_PROFILE_FILE: "qiskit-%p-%m.profraw" SETUPTOOLS_ENABLE_FEATURES: "legacy-editable" + - name: Generate unittest coverage report run: | set -e pip install -r requirements-dev.txt qiskit-aer stestr run - ./grcov . --binary-path ./target/debug/ -s . --llvm --parallel -t lcov --branch --ignore-not-existing --ignore "/*" -o ./rust_unittest.info - mkdir rust_lcov - mv ./rust_unittest.info rust_lcov/. - rm *profraw + # We set the --source-dir to '.' because we want all paths to appear relative to the repo + # root (we need to combine them with the Python ones), but we only care about `grcov` + # keeping the `src/*` files; we don't care about coverage in dependencies. + grcov . --binary-path target/debug/ --source-dir . --output-type lcov --output-path rust.info --llvm --branch --parallel --keep-only 'src/*' env: QISKIT_TEST_CAPTURE_STREAMS: 1 QISKIT_PARALLEL: FALSE - PYTHON: "coverage3 run --source qiskit --parallel-mode" - - name: Generate parallel_map test - run: | - set -e - coverage3 run --source qiskit --parallel-mode ./tools/verify_parallel_map.py - ./grcov . --binary-path ./target/debug/ -s . --llvm --parallel -t lcov --branch --ignore-not-existing --ignore "/*" -o ./rust_parallel_map.info - mv rust_lcov/rust_unittest.info . - env: - QISKIT_TEST_CAPTURE_STREAMS: 1 - QISKIT_PARALLEL: FALSE - PYTHON: "coverage3 run --source qiskit --parallel-mode" + PYTHON: "coverage run --source qiskit --parallel-mode" + - name: Convert to lcov and combine data run: | set -e - coverage3 combine - coveragepy-lcov --output_file_path python.info - lcov --add-tracefile python.info -a rust_unittest.info -a rust_parallel_map.info -o combined.info - lcov --remove combined.info "target/*" -o coveralls.info + coverage combine + coverage lcov -o python.info + lcov --add-tracefile python.info --add-tracefile rust.info --output-file coveralls.info + - name: Coveralls uses: coverallsapp/github-action@master with: diff --git a/constraints.txt b/constraints.txt index a7ab23edb200..852c3bbc40ea 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,24 +1,3 @@ # jsonschema pinning needed due nbformat==5.1.3 using deprecated behaviour in # 4.0+. The pin can be removed after nbformat is updated. jsonschema==3.2.0 - -# pyparsing restrictions are needed because pyparsing 3.0.0 and 3.0.1 break -# matplotlib's mathtex extensions. At the time of writing (2021-10-25), the -# docs build is pinned to matplotlib<3.4 (current) because of deprecations, so -# as we won't get matplotlib upgrades by default, this constraint likely can't -# be removed until we can unpin matplotlib. -pyparsing<3.0.0 - -# Jinja2 3.1.0 is incompatible with sphinx and/or jupyter until they are updated -# to work with the new jinja version (the jinja maintainers aren't going to -# fix things) pin to the previous working version. -jinja2==3.0.3 - -# jupyter-core 5.0.0 started emitting deprecation warnings with ipywidgets via -# a seaborn import. This pin can be removed when compatibility with those -# packages is fixed -jupyter-core==4.11.2 - -# ipywidgets 8.0.3 started emitting deprecation warnings via a seaborn import. -# This pin can be removed when compatibility with those packages is fixed. -ipywidgets<8.0.3 diff --git a/qiskit/algorithms/optimizers/snobfit.py b/qiskit/algorithms/optimizers/snobfit.py index f74d8ca85153..8c87fd60b41d 100644 --- a/qiskit/algorithms/optimizers/snobfit.py +++ b/qiskit/algorithms/optimizers/snobfit.py @@ -15,6 +15,7 @@ from typing import Any, Dict, Optional, Callable, Tuple, List import numpy as np +from qiskit.exceptions import QiskitError from qiskit.utils import optionals as _optionals from .optimizer import Optimizer, OptimizerSupportLevel, OptimizerResult, POINT @@ -50,7 +51,17 @@ def __init__( Raises: MissingOptionalLibraryError: scikit-quant or SQSnobFit not installed + QiskitError: If NumPy 1.24.0 or above is installed. + See https://github.com/scikit-quant/scikit-quant/issues/24 for more details. """ + # check version + version = tuple(map(int, np.__version__.split("."))) + if version >= (1, 24, 0): + raise QiskitError( + "SnobFit is incompatible with NumPy 1.24.0 or above, please " + "install a previous version. See also scikit-quant/scikit-quant#24." + ) + super().__init__() self._maxiter = maxiter self._maxfail = maxfail diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py index c40ae57bb148..4f9957138813 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -131,11 +131,13 @@ def __eq__(self, other): pass try: - if numpy.shape(self_param) == numpy.shape(other_param) and numpy.allclose( + self_asarray = numpy.asarray(self_param) + other_asarray = numpy.asarray(other_param) + if numpy.shape(self_asarray) == numpy.shape(other_asarray) and numpy.allclose( self_param, other_param, atol=_CUTOFF_PRECISION, rtol=0 ): continue - except TypeError: + except (ValueError, TypeError): pass try: diff --git a/qiskit/quantum_info/operators/channel/kraus.py b/qiskit/quantum_info/operators/channel/kraus.py index 9dd06040148d..e61f0152405c 100644 --- a/qiskit/quantum_info/operators/channel/kraus.py +++ b/qiskit/quantum_info/operators/channel/kraus.py @@ -86,10 +86,12 @@ def __init__(self, data, input_dims=None, output_dims=None): # If the input is a list or tuple we assume it is a list of Kraus # matrices, if it is a numpy array we assume that it is a single Kraus # operator + # TODO properly handle array construction from ragged data (like tuple(np.ndarray, None)) + # and document these accepted input cases. See also Qiskit/qiskit-terra#9307. if isinstance(data, (list, tuple, np.ndarray)): # Check if it is a single unitary matrix A for channel: # E(rho) = A * rho * A^\dagger - if isinstance(data, np.ndarray) or np.array(data).ndim == 2: + if _is_matrix(data): # Convert single Kraus op to general Kraus pair kraus = ([np.asarray(data, dtype=complex)], None) shape = kraus[0][0].shape @@ -320,5 +322,13 @@ def _multiply(self, other): return ret +def _is_matrix(data): + # return True if data is a 2-d array/tuple/list + if not isinstance(data, np.ndarray): + data = np.array(data, dtype=object) + + return data.ndim == 2 + + # Update docstrings for API docs generate_apidocs(Kraus) diff --git a/qiskit/test/base.py b/qiskit/test/base.py index 2fc5dbc21850..737101b75c47 100644 --- a/qiskit/test/base.py +++ b/qiskit/test/base.py @@ -223,13 +223,13 @@ def setUpClass(cls): r"The QuantumCircuit.cu.", r"The CXDirection pass has been deprecated", r"The pauli_basis function with PauliTable.*", - # TODO: remove the following ignore after seaborn 0.12.0 releases - r"distutils Version classes are deprecated. Use packaging\.version", - # Internal deprecation warning emitted by jupyter client when - # calling nbconvert in python 3.10 - r"There is no current event loop", # Caused by internal scikit-learn scipy usage r"The 'sym_pos' keyword is deprecated and should be replaced by using", + # jupyter_client 7.4.8 uses deprecated shims in pyzmq that raise warnings with pyzmq 25. + # These are due to be fixed by jupyter_client 8, see: + # - https://github.com/jupyter/jupyter_client/issues/913 + # - https://github.com/jupyter/jupyter_client/pull/842 + r"zmq\.eventloop\.ioloop is deprecated in pyzmq .*", ] for msg in allow_DeprecationWarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) diff --git a/qiskit/utils/classtools.py b/qiskit/utils/classtools.py index 71c17754bdad..1e58b1ad2b91 100644 --- a/qiskit/utils/classtools.py +++ b/qiskit/utils/classtools.py @@ -25,11 +25,6 @@ _MAGIC_STATICMETHODS = {"__new__"} _MAGIC_CLASSMETHODS = {"__init_subclass__", "__prepare__"} -# `type` itself has several methods (mostly dunders). When we are wrapping those names, we need to -# make sure that we don't interfere with `type.__getattribute__`'s handling that circumvents the -# normal inheritance rules when appropriate. -_TYPE_METHODS = set(dir(type)) - class _lift_to_method: # pylint: disable=invalid-name """A decorator that ensures that an input callable object implements ``__get__``. It is @@ -146,16 +141,6 @@ def wrap_method(cls: Type, name: str, *, before: Callable = None, after: Callabl # The best time to apply decorators to methods is before they are bound (e.g. by using function # decorators during the class definition), but if we're making a class decorator, we can't do # that. We need the actual definition of the method, so we have to dodge the normal output of - # `type.__getattribute__`, which evalutes descriptors if it finds them, unless the name we're - # looking for is defined on `type` itself. In that case, we need the attribute getter to - # correctly return the underlying object, not the one that `type` defines for its own purposes. - attribute_getter = type.__getattribute__ if name in _TYPE_METHODS else object.__getattribute__ - for cls_ in inspect.getmro(cls): - try: - method = attribute_getter(cls_, name) - break - except AttributeError: - pass - else: - raise ValueError(f"Method '{name}' is not defined for class '{cls.__name__}'") + # `type.__getattribute__`, which evalutes descriptors if it finds them. + method = inspect.getattr_static(cls, name) setattr(cls, name, _WrappedMethod(method, before, after)) diff --git a/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml new file mode 100644 index 000000000000..146e01b903c5 --- /dev/null +++ b/releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml @@ -0,0 +1,11 @@ +--- +fixes: + - | + Fixed handling of some ``classmethod``s by + :func:`~qiskit.utils.wrap_method` in Python 3.11. Previously, in Python + 3.11, ``wrap_method`` would wrap the unbounded function associated with the + ``classmethod`` and then fail when invoked because the class object usually + bound to the ``classmethod`` was not passed to the function. Starting in + Python 3.11.1, this issue affected :class:`~qiskit.test.QiskitTestCase`, + preventing it from being imported by other test code. Fixed `#9291 + `__. diff --git a/requirements-dev.txt b/requirements-dev.txt index 4677b5f69155..2908e8213db2 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -coverage>=4.4.0 +coverage>=4.4.0,<7.0 hypothesis>=4.24.3 python-constraint>=1.4 ipython<7.22.0 diff --git a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py b/test/python/algorithms/optimizers/test_optimizers_scikitquant.py index 63dd95c44a84..70716c4e75c5 100644 --- a/test/python/algorithms/optimizers/test_optimizers_scikitquant.py +++ b/test/python/algorithms/optimizers/test_optimizers_scikitquant.py @@ -17,6 +17,7 @@ from ddt import ddt, data, unpack +import numpy from qiskit import BasicAer from qiskit.circuit.library import RealAmplitudes from qiskit.utils import QuantumInstance, algorithm_globals @@ -63,6 +64,10 @@ def test_bobyqa(self): except MissingOptionalLibraryError as ex: self.skipTest(str(ex)) + @unittest.skipIf( + tuple(map(int, numpy.__version__.split("."))) >= (1, 24, 0), + "scikit's SnobFit currently incompatible with NumPy 1.24.0.", + ) def test_snobfit(self): """SNOBFIT optimizer test.""" try: @@ -71,6 +76,10 @@ def test_snobfit(self): except MissingOptionalLibraryError as ex: self.skipTest(str(ex)) + @unittest.skipIf( + tuple(map(int, numpy.__version__.split("."))) >= (1, 24, 0), + "scikit's SnobFit currently incompatible with NumPy 1.24.0.", + ) @data((None,), ([(-1, 1), (None, None)],)) @unpack def test_snobfit_missing_bounds(self, bounds): diff --git a/test/python/test_util.py b/test/python/test_util.py index 3dc5e9ec39d7..b574ff8390b0 100644 --- a/test/python/test_util.py +++ b/test/python/test_util.py @@ -39,6 +39,6 @@ def test_triu_to_dense(self): m = np.random.randint(-100, 100, size=(n, n)) symm = (m + m.T) / 2 - triu = np.array([[symm[i, j] for i in range(j, n)] for j in range(n)]) + triu = [[symm[i, j] for i in range(j, n)] for j in range(n)] self.assertTrue(np.array_equal(symm, triu_to_dense(triu))) diff --git a/test/python/utils/test_classtools.py b/test/python/utils/test_classtools.py index 272c3e0a6146..b373675ea569 100644 --- a/test/python/utils/test_classtools.py +++ b/test/python/utils/test_classtools.py @@ -529,5 +529,5 @@ def test_raises_on_invalid_name(self): class Dummy: pass - with self.assertRaisesRegex(ValueError, "Method 'bad' is not defined for class 'Dummy'"): + with self.assertRaisesRegex(AttributeError, "bad"): wrap_method(Dummy, "bad", before=lambda self: None)