Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions qiskit/extensions/quantum_initializer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,42 @@

"""Initialize qubit registers to desired arbitrary state."""

# pylint: disable=wrong-import-position

import os
import platform
import warnings

import numpy as np

# The PyPI-distributed versions of Numpy 1.25 and 1.26 were compiled for macOS x86_64 using a
# compiler that caused a bug in the complex-multiply ufunc when AVX2 extensions are enabled. This
# severely affects the `Isometry` definition code to the point of the returned definitions being
# entirely unsound, so we need to warn users. See:
#
# - https://github.com/Qiskit/qiskit/issues/10305
# - https://github.com/numpy/numpy/issues/24000
_KNOWN_AFFECTED_NUMPY_VERSIONS = ("1.25.0", "1.25.1", "1.25.2", "1.26.0")
_IS_BAD_NUMPY = (
os.environ.get("QISKIT_CMUL_AVX2_GOOD_NUMPY", "0") != "1"
and platform.system() == "Darwin"
and platform.machine() == "x86_64"
and np.__version__ in _KNOWN_AFFECTED_NUMPY_VERSIONS
and np.core._multiarray_umath.__cpu_features__.get("AVX2", False)
)


def _warn_if_bad_numpy(usage):
if not _IS_BAD_NUMPY:
return
msg = (
f"On Intel macOS, NumPy {np.__version__} from PyPI has a bug in the complex-multiplication"
f" ufunc that severely affects {usage}."
" See https://qisk.it/cmul-avx2-numpy-bug for work-around information."
)
warnings.warn(msg, RuntimeWarning, stacklevel=3)


from .squ import SingleQubitUnitary
from .uc_pauli_rot import UCPauliRotGate
from .ucrz import UCRZGate
Expand Down
3 changes: 3 additions & 0 deletions qiskit/extensions/quantum_initializer/uc.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from qiskit.circuit.exceptions import CircuitError
from qiskit.exceptions import QiskitError
from qiskit.quantum_info.synthesis import OneQubitEulerDecomposer
from qiskit.extensions.quantum_initializer import _warn_if_bad_numpy
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I normally address lint's cyclic import complaints with relative imports, but I guess here you'd have to move _warn_if_bad_numpy to a separate file to be able to do that, right?


_EPS = 1e-10 # global variable used to chop very small numbers to zero
_DECOMPOSER1Q = OneQubitEulerDecomposer("U3")
Expand Down Expand Up @@ -134,6 +135,8 @@ def _dec_ucg(self):
up_to_diagonal=True, the circuit implements the gate up to a diagonal gate and
the diagonal gate is also returned.
"""
_warn_if_bad_numpy("synthesis of multiplexed gates")

diag = np.ones(2**self.num_qubits).tolist()
q = QuantumRegister(self.num_qubits)
q_controls = q[1:]
Expand Down
18 changes: 18 additions & 0 deletions releasenotes/notes/numpy-avx2-cmul-ucgate-6c8708fb30f42f3f.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
issues:
- |
The PyPI versions of the NumPy 1.25 series and the current most recent release 1.26.0 are known
to have inconsistent behaviour with the complex `multiply` ufunc when running on Intel Macs with
AVX2 CPU extensions. This bug destabilises the synthesis of :class:`.UCGate`, which is used by
:class:`.Isometry` and :meth:`.UnitaryGate.control`, to the degree of it returning completely
invalid results.

This bug only affects Intel Macs with AVX2 CPU extensions enabled with certain versions of NumPy.
If the bad code paths are used, a :exc:`RuntimeWarning` will be issued. If you are affected,
you can install an older version of NumPy (the 1.24 series is known good), or you can disable
NumPy's use of AVX2 extensions by setting the environment variable
`NPY_DISABLE_CPU_FEATURES=AVX2` while importing `numpy`. See
https://qisk.it/cmul-avx2-numpy-bug for more detail.

This bug also affects all prior versions of Qiskit with :class:`.UCGate` if the bad combination
of OS, CPU and NumPy version are present, but this is the first version that issues a warning.
2 changes: 2 additions & 0 deletions test/python/circuit/test_controlled_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import qiskit.circuit.library.standard_gates as allGates
from qiskit.extensions import UnitaryGate
from qiskit.circuit.library.standard_gates.multi_control_rotation_gates import _mcsu2_real_diagonal
from qiskit.extensions.quantum_initializer import _IS_BAD_NUMPY

from .gate_utils import _get_free_params

Expand Down Expand Up @@ -843,6 +844,7 @@ def test_controlled_unitary(self, num_ctrl_qubits):
self.assertTrue(matrix_equal(cop_mat, test_op.data))

@data(1, 2, 3, 4, 5)
@unittest.skipIf(_IS_BAD_NUMPY, "known-bad OS+NumPy combination for Isometry")
def test_controlled_random_unitary(self, num_ctrl_qubits):
"""Test the matrix data of an Operator based on a random UnitaryGate."""
num_target = 2
Expand Down
2 changes: 2 additions & 0 deletions test/python/circuit/test_isometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
from qiskit.test import QiskitTestCase
from qiskit.compiler import transpile
from qiskit.quantum_info import Operator
from qiskit.extensions.quantum_initializer import _IS_BAD_NUMPY
from qiskit.extensions.quantum_initializer.isometry import Isometry


@ddt
@unittest.skipIf(_IS_BAD_NUMPY, "known-bad OS+NumPy combination for Isometry")
class TestIsometry(QiskitTestCase):
"""Qiskit isometry tests."""

Expand Down
2 changes: 2 additions & 0 deletions test/python/circuit/test_uc.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import numpy as np
from scipy.linalg import block_diag

from qiskit.extensions.quantum_initializer import _IS_BAD_NUMPY
from qiskit.extensions.quantum_initializer.uc import UCGate

from qiskit import QuantumCircuit, QuantumRegister, BasicAer, execute
Expand All @@ -37,6 +38,7 @@


@ddt
@unittest.skipIf(_IS_BAD_NUMPY, "known-bad OS+NumPy combination for UCGate")
class TestUCGate(QiskitTestCase):
"""Qiskit UCGate tests."""

Expand Down
4 changes: 4 additions & 0 deletions test/python/circuit/test_unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
"""UnitaryGate tests"""

import json
import unittest

import numpy
from numpy.testing import assert_allclose

import qiskit
from qiskit.extensions.quantum_initializer import _IS_BAD_NUMPY
from qiskit.extensions.unitary import UnitaryGate
from qiskit.test import QiskitTestCase
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
Expand Down Expand Up @@ -303,6 +306,7 @@ def test_unitary_decomposition_via_definition_2q(self):
mat = numpy.array([[0, 0, 1, 0], [0, 0, 0, -1], [1, 0, 0, 0], [0, -1, 0, 0]])
self.assertTrue(numpy.allclose(Operator(UnitaryGate(mat).definition).data, mat))

@unittest.skipIf(_IS_BAD_NUMPY, "known-bad OS+NumPy combination for Isometry")
def test_unitary_control(self):
"""Test parameters of controlled - unitary."""
mat = numpy.array([[0, 1], [1, 0]])
Expand Down