Skip to content
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
)
Comment on lines -642 to +644
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm guessing this is int for consistency with other methods in this class?

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