Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix pauli.evolve dtype casting error (backport #8877) #8884

Merged
merged 1 commit into from
Oct 12, 2022
Merged
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
32 changes: 16 additions & 16 deletions qiskit/quantum_info/operators/symplectic/base_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ def compose(self, other, qargs=None, front=False, inplace=False):
# 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)
Expand Down Expand Up @@ -185,7 +185,7 @@ def conjugate(self):
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))
Expand Down Expand Up @@ -388,7 +388,8 @@ def _from_array(z, x, phase=0):
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
Expand Down Expand Up @@ -601,23 +602,23 @@ 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


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


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


Expand All @@ -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


Expand All @@ -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


Expand All @@ -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


Expand All @@ -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)
8 changes: 5 additions & 3 deletions qiskit/quantum_info/operators/symplectic/pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,12 +286,12 @@ def settings(self) -> Dict:
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):
Expand Down Expand Up @@ -639,7 +639,9 @@ def _from_scalar_op(cls, op):
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
Expand Down
4 changes: 2 additions & 2 deletions qiskit/quantum_info/operators/symplectic/pauli_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,12 @@ def equiv(self, other):
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):
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/fix_8438-159e67ecb6765d08.yaml
Original file line number Diff line number Diff line change
@@ -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>`_
28 changes: 28 additions & 0 deletions test/python/quantum_info/operators/symplectic/test_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,34 @@ def test_evolve_clifford2(self, gate, label):
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2034,6 +2034,40 @@ def test_evolve_clifford2(self, gate):
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"""
Expand Down