mirror of https://github.com/Qiskit/qiskit.git
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:
parent
75fe9bb157
commit
e6d23d5305
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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>`_
|
|
@ -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)
|
||||
|
|
|
@ -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"""
|
||||
|
|
Loading…
Reference in New Issue