From 2a577297fffd41734dd4a29a87bff0381f505946 Mon Sep 17 00:00:00 2001 From: Julien Gacon Date: Tue, 20 Dec 2022 18:49:55 +0100 Subject: [PATCH 1/4] Fix NumPy 1.24.0 compatibility and pin `coverage<7.0` (#9305) * fix Kraus from (array, None) * fix triu_to_dense test * fix instruction comparison * skip snobfit if numpy 1.24.0 or above is installed Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> * pin coverage <7.0 * add links to Kraus and snobfit issues * retrigger CI Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com> (cherry picked from commit 9733fc0ca9e548da976e369116c857653c03db4f) --- constraints.txt | 5 +++++ qiskit/algorithms/optimizers/snobfit.py | 11 +++++++++++ qiskit/circuit/instruction.py | 6 ++++-- qiskit/quantum_info/operators/channel/kraus.py | 12 +++++++++++- requirements-dev.txt | 2 +- .../optimizers/test_optimizers_scikitquant.py | 9 +++++++++ test/python/test_util.py | 2 +- 7 files changed, 42 insertions(+), 5 deletions(-) diff --git a/constraints.txt b/constraints.txt index a7ab23edb200..c7ffa9836d4a 100644 --- a/constraints.txt +++ b/constraints.txt @@ -22,3 +22,8 @@ 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 + +# coverage 7.0.0 breaks with Python 3.8.15 upon the import +# from coverage.files import FnmatchMatcher +# since this is in Python 3.8 itself and not Qiskit, pinning this for now. +coverage<7.0 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/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))) From a77284a44b864a2a84224e26e1d0ec55dbdd6a42 Mon Sep 17 00:00:00 2001 From: Will Shanks Date: Mon, 9 Jan 2023 19:24:07 -0500 Subject: [PATCH 2/4] Update `qiskit.utils.wrap_method` for Python 3.11 (#9310) * Revert "[Test] Pin maximum python version in CI to <3.11.1 (#9296)" This reverts commit 07e0a2fc79bada7c1fbf0594f4ad33934f70b7e2. * Do not treat __init_subclass__ as a special type method * Release note * Apply suggestions from code review Co-authored-by: Julien Gacon * Use inspect.getattr_static to bypass descriptor call * Update release note * Update wrap_method test * Adjust wording on release note Co-authored-by: Julien Gacon Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit 0344a1c94b8eb8206da18cc74d9bdc70ad203c9a) --- qiskit/utils/classtools.py | 19 ++----------------- .../wrap-method-311-147d254d4b40e805.yaml | 11 +++++++++++ test/python/utils/test_classtools.py | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/wrap-method-311-147d254d4b40e805.yaml 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/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) From a02f4fcc96d92c7fbf7f9f3aa3a7be2087ea54d2 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Thu, 12 Jan 2023 13:38:30 +0000 Subject: [PATCH 3/4] Relax constraints on jupyter-core and ipywidgets (#9364) These were originally added in #9105 and #9272 respectively, but the original problem package `seaborn` has released since then, which may have fixed things. This removes some now-unnecessary suppressions from image-related packages, and adds the new suppression for the pyzmq problem, which is Jupyter's domain to handle. The extra environment variable in the images test run is to eagerly move to new default behaviour starting in jupyter-core 6; there is no need for us to pin the package too low, since this warning is just encouraging people to proactively test the new behaviour, and it doesn't cause our suite problems. --- .azure/test-linux.yml | 5 +++++ constraints.txt | 26 -------------------------- qiskit/test/base.py | 10 +++++----- 3 files changed, 10 insertions(+), 31 deletions(-) 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/constraints.txt b/constraints.txt index c7ffa9836d4a..852c3bbc40ea 100644 --- a/constraints.txt +++ b/constraints.txt @@ -1,29 +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 - -# coverage 7.0.0 breaks with Python 3.8.15 upon the import -# from coverage.files import FnmatchMatcher -# since this is in Python 3.8 itself and not Qiskit, pinning this for now. -coverage<7.0 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) From 4aafcc42a73c5758f1eff068ed887152478304e7 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Wed, 11 Jan 2023 22:24:15 +0000 Subject: [PATCH 4/4] Refactor coverage CI workflow (#9361) This relaxes the constraint on `coverage` added in #9305. The issue there is actually the now unmaintained `coveragepy-lcov` package is not compatible with Coverage.py 7.0. However, we only needed `coveragepy-lcov` to convert Coverage's format into LCOV, which is a feature Coverage has had itself since version 6.0. This commit also updates some parts of the coverage workflow that were old: - there are new versions of the Actions `checkout` and `setup-python`, which swap to using Node 16 rather than Node 12, which is deprecated in GHA - `grcov` is packaged and installable from `cargo` now, rather than needing a manual hard-coded pull from GitHub - we can have `grcov` only keep the parts we care about immediately, rather than converting everything and later discarding it Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> (cherry picked from commit 7955d92d3e8c6eb8055d29ce334a6fffeeb1310f) --- .github/workflows/coverage.yml | 41 ++++++++++++++-------------------- 1 file changed, 17 insertions(+), 24 deletions(-) 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: