Fix pauli.evolve dtype casting error (#8877)

* Fix pauli phase evolve

* Update for unsigned int phases

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Christopher J. Wood 2022-10-12 13:06:36 -04:00 committed by GitHub
parent 75fe9bb157
commit e6d23d5305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 21 deletions

View File

@ -131,9 +131,9 @@ class BasePauli(BaseOperator, AdjointMixin, MultiplyMixin):
# Get phase shift
phase = self._phase + other._phase
if front:
phase += 2 * _count_y(x1, z2)
phase += 2 * _count_y(x1, z2, dtype=phase.dtype)
else:
phase += 2 * _count_y(x2, z1)
phase += 2 * _count_y(x2, z1, dtype=phase.dtype)
# Update Pauli
x = np.logical_xor(x1, x2)
@ -185,7 +185,7 @@ class BasePauli(BaseOperator, AdjointMixin, MultiplyMixin):
def transpose(self):
"""Return the transpose of each Pauli in the list."""
# Transpose sets Y -> -Y. This has effect on changing the phase
parity_y = self._count_y() % 2
parity_y = self._count_y(dtype=self._phase.dtype) % 2
if np.all(parity_y == 0):
return self
return BasePauli(self._z, self._x, np.mod(self._phase + 2 * parity_y, 4))
@ -388,7 +388,8 @@ class BasePauli(BaseOperator, AdjointMixin, MultiplyMixin):
raise QiskitError("z and x vectors are different size.")
# Convert group phase convention to internal ZX-phase conversion.
base_phase = np.mod(_count_y(base_x, base_z) + phase, 4)
dtype = getattr(phase, "dtype", None)
base_phase = np.mod(_count_y(base_x, base_z, dtype=dtype) + phase, 4)
return base_z, base_x, base_phase
@staticmethod
@ -601,7 +602,7 @@ def _evolve_h(base_pauli, qubit):
z = base_pauli._z[:, qubit].copy()
base_pauli._x[:, qubit] = z
base_pauli._z[:, qubit] = x
base_pauli._phase += 2 * np.logical_and(x, z).T
base_pauli._phase += 2 * np.logical_and(x, z).T.astype(base_pauli._phase.dtype)
return base_pauli
@ -609,7 +610,7 @@ def _evolve_s(base_pauli, qubit):
"""Update P -> S.P.Sdg"""
x = base_pauli._x[:, qubit]
base_pauli._z[:, qubit] ^= x
base_pauli._phase += x.T
base_pauli._phase += x.T.astype(base_pauli._phase.dtype)
return base_pauli
@ -617,7 +618,7 @@ def _evolve_sdg(base_pauli, qubit):
"""Update P -> Sdg.P.S"""
x = base_pauli._x[:, qubit]
base_pauli._z[:, qubit] ^= x
base_pauli._phase -= x.T
base_pauli._phase -= x.T.astype(base_pauli._phase.dtype)
return base_pauli
@ -629,19 +630,21 @@ def _evolve_i(base_pauli, qubit):
def _evolve_x(base_pauli, qubit):
"""Update P -> X.P.X"""
base_pauli._phase += 2 * base_pauli._z[:, qubit].T
base_pauli._phase += 2 * base_pauli._z[:, qubit].T.astype(base_pauli._phase.dtype)
return base_pauli
def _evolve_y(base_pauli, qubit):
"""Update P -> Y.P.Y"""
base_pauli._phase += 2 * base_pauli._x[:, qubit].T + 2 * base_pauli._z[:, qubit].T
xp = base_pauli._x[:, qubit].T.astype(base_pauli._phase.dtype)
zp = base_pauli._z[:, qubit].T.astype(base_pauli._phase.dtype)
base_pauli._phase += 2 * (xp + zp)
return base_pauli
def _evolve_z(base_pauli, qubit):
"""Update P -> Z.P.Z"""
base_pauli._phase += 2 * base_pauli._x[:, qubit].T
base_pauli._phase += 2 * base_pauli._x[:, qubit].T.astype(base_pauli._phase.dtype)
return base_pauli
@ -658,7 +661,7 @@ def _evolve_cz(base_pauli, q1, q2):
x2 = base_pauli._x[:, q2].copy()
base_pauli._z[:, q1] ^= x2
base_pauli._z[:, q2] ^= x1
base_pauli._phase += 2 * np.logical_and(x1, x2).T
base_pauli._phase += 2 * np.logical_and(x1, x2).T.astype(base_pauli._phase.dtype)
return base_pauli
@ -670,7 +673,7 @@ def _evolve_cy(base_pauli, qctrl, qtrgt):
base_pauli._x[:, qtrgt] ^= x1
base_pauli._z[:, qtrgt] ^= x1
base_pauli._z[:, qctrl] ^= np.logical_xor(x2, z2)
base_pauli._phase += x1 + 2 * np.logical_and(x1, x2).T
base_pauli._phase += x1 + 2 * np.logical_and(x1, x2).T.astype(base_pauli._phase.dtype)
return base_pauli
@ -687,7 +690,4 @@ def _evolve_swap(base_pauli, q1, q2):
def _count_y(x, z, dtype=None):
"""Count the number of I Pauli's"""
axis = 1
if dtype is None:
dtype = np.min_scalar_type(x.shape[axis])
return (x & z).sum(axis=axis, dtype=dtype)
return (x & z).sum(axis=1, dtype=dtype)

View File

@ -286,12 +286,12 @@ class Pauli(BasePauli):
def phase(self):
"""Return the group phase exponent for the Pauli."""
# Convert internal ZX-phase convention of BasePauli to group phase
return np.mod(self._phase - self._count_y(), 4)[0]
return np.mod(self._phase - self._count_y(dtype=self._phase.dtype), 4)[0]
@phase.setter
def phase(self, value):
# Convert group phase convention to internal ZX-phase convention
self._phase[:] = np.mod(value + self._count_y(), 4)
self._phase[:] = np.mod(value + self._count_y(dtype=self._phase.dtype), 4)
@property
def x(self):
@ -639,7 +639,9 @@ class Pauli(BasePauli):
raise QiskitError(f"{op} is not an N-qubit identity")
base_z = np.zeros((1, op.num_qubits), dtype=bool)
base_x = np.zeros((1, op.num_qubits), dtype=bool)
base_phase = np.mod(cls._phase_from_complex(op.coeff) + _count_y(base_x, base_z), 4)
base_phase = np.mod(
cls._phase_from_complex(op.coeff) + _count_y(base_x, base_z), 4, dtype=int
)
return base_z, base_x, base_phase
@classmethod

View File

@ -237,12 +237,12 @@ class PauliList(BasePauli, LinearMixin, GroupMixin):
def phase(self):
"""Return the phase exponent of the PauliList."""
# Convert internal ZX-phase convention to group phase convention
return np.mod(self._phase - self._count_y(), 4)
return np.mod(self._phase - self._count_y(dtype=self._phase.dtype), 4)
@phase.setter
def phase(self, value):
# Convert group phase convetion to internal ZX-phase convention
self._phase[:] = np.mod(value + self._count_y(), 4)
self._phase[:] = np.mod(value + self._count_y(dtype=self._phase.dtype), 4)
@property
def x(self):

View File

@ -0,0 +1,7 @@
---
fixes:
- |
Fixes issue where :meth:`~.Pauli.evolve` and `~.PauliList.evolve` would
raise a dtype error when evolving by certain Clifford gates which
modified the Pauli's phase.
Fixes `issue #8438 <https://github.com/Qiskit/qiskit-terra/issues/8438>`_

View File

@ -404,6 +404,34 @@ class TestPauli(QiskitTestCase):
self.assertEqual(value, value_h)
self.assertEqual(value_inv, value_s)
@data(
*it.product(
(
IGate(),
XGate(),
YGate(),
ZGate(),
HGate(),
SGate(),
SdgGate(),
CXGate(),
CYGate(),
CZGate(),
SwapGate(),
),
[int, np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64],
)
)
@unpack
def test_phase_dtype_evolve_clifford(self, gate, dtype):
"""Test phase dtype for evolve method for Clifford gates."""
z = np.ones(gate.num_qubits, dtype=bool)
x = np.ones(gate.num_qubits, dtype=bool)
phase = (np.sum(z & x) % 4).astype(dtype)
paulis = Pauli((z, x, phase))
evo = paulis.evolve(gate)
self.assertEqual(evo.phase.dtype, dtype)
def test_evolve_clifford_qargs(self):
"""Test evolve method for random Clifford"""
cliff = random_clifford(3, seed=10)

View File

@ -2034,6 +2034,40 @@ class TestPauliListMethods(QiskitTestCase):
self.assertListEqual(value, value_h)
self.assertListEqual(value_inv, value_s)
def test_phase_dtype_evolve_clifford(self):
"""Test phase dtype during evolve method for Clifford gates."""
gates = (
IGate(),
XGate(),
YGate(),
ZGate(),
HGate(),
SGate(),
SdgGate(),
CXGate(),
CYGate(),
CZGate(),
SwapGate(),
)
dtypes = [
int,
np.int8,
np.uint8,
np.int16,
np.uint16,
np.int32,
np.uint32,
np.int64,
np.uint64,
]
for gate, dtype in itertools.product(gates, dtypes):
z = np.ones(gate.num_qubits, dtype=bool)
x = np.ones(gate.num_qubits, dtype=bool)
phase = (np.sum(z & x) % 4).astype(dtype)
paulis = Pauli((z, x, phase))
evo = paulis.evolve(gate)
self.assertEqual(evo.phase.dtype, dtype)
@combine(phase=(True, False))
def test_evolve_clifford_qargs(self, phase):
"""Test evolve method for random Clifford"""