Skip to content

Commit

Permalink
Issue warning on known-bad combinations for UCGate synthesis
Browse files Browse the repository at this point in the history
The combination of AVX2-enabled Intel Macs with versions of NumPy
compiled with Apple clang 14.0.0 has problems with the complex128
`multiply` ufunc that makes it non-deterministic.  The PyPI releases of
the entire NumPy 1.25 series and the current newest 1.26.0 are all known
to have this issue.

This causes a severe instability in the `UCGate` synthesis code, to the
degree of the returned results being (non-deterministically) completely
invalid.  This commit causes Qiskit to query the host platform and
runtime NumPy features to guess if the bug is likely to be present; the
results are so incorrect that we need to warn users so they can work
around them.
  • Loading branch information
jakelishman committed Sep 27, 2023
1 parent 6e80e89 commit f887869
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 0 deletions.
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

_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

0 comments on commit f887869

Please sign in to comment.