From 553f274a883e876cb6ffc338556a366af13ea918 Mon Sep 17 00:00:00 2001 From: "Christopher J. Wood" Date: Tue, 11 Oct 2022 15:10:39 -0400 Subject: [PATCH 1/2] Fix pauli phase evolve --- .../operators/symplectic/base_pauli.py | 20 ++++++------- .../operators/symplectic/pauli.py | 8 ++++-- .../operators/symplectic/pauli_list.py | 4 +-- .../notes/fix_8438-159e67ecb6765d08.yaml | 7 +++++ .../operators/symplectic/test_pauli.py | 28 +++++++++++++++++++ .../operators/symplectic/test_pauli_list.py | 25 +++++++++++++++++ 6 files changed, 76 insertions(+), 16 deletions(-) create mode 100644 releasenotes/notes/fix_8438-159e67ecb6765d08.yaml diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 3e03838cd695..8b992574edd6 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -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) @@ -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)) @@ -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 @@ -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).astype(base_pauli._phase.dtype).T return base_pauli @@ -658,7 +659,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).astype(base_pauli._phase.dtype).T return base_pauli @@ -670,7 +671,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).astype(base_pauli._phase.dtype).T return base_pauli @@ -687,7 +688,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) diff --git a/qiskit/quantum_info/operators/symplectic/pauli.py b/qiskit/quantum_info/operators/symplectic/pauli.py index 941ae4d5228c..b2663222d408 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli.py +++ b/qiskit/quantum_info/operators/symplectic/pauli.py @@ -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): @@ -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 diff --git a/qiskit/quantum_info/operators/symplectic/pauli_list.py b/qiskit/quantum_info/operators/symplectic/pauli_list.py index 188711e4bacb..293e0a641e8a 100644 --- a/qiskit/quantum_info/operators/symplectic/pauli_list.py +++ b/qiskit/quantum_info/operators/symplectic/pauli_list.py @@ -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): diff --git a/releasenotes/notes/fix_8438-159e67ecb6765d08.yaml b/releasenotes/notes/fix_8438-159e67ecb6765d08.yaml new file mode 100644 index 000000000000..189827703523 --- /dev/null +++ b/releasenotes/notes/fix_8438-159e67ecb6765d08.yaml @@ -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 `_ diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index c952a3a8fb93..7d95511f5baa 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -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.int16, np.int32, np.int64], + ) + ) + @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) diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index 70b4bc267308..de7513719e8f 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -2034,6 +2034,31 @@ 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.int16, np.int32, np.int64] + evolved_dtypes = [] + 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""" From 8be631dbe5783da1a554792c5b8643193867a5b2 Mon Sep 17 00:00:00 2001 From: "Christopher J. Wood" Date: Tue, 11 Oct 2022 15:25:34 -0400 Subject: [PATCH 2/2] Update for unsigned int phases --- .../operators/symplectic/base_pauli.py | 18 ++++++++++-------- .../operators/symplectic/test_pauli.py | 2 +- .../operators/symplectic/test_pauli_list.py | 13 +++++++++++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 8b992574edd6..1f9e740361c1 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -602,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).astype(base_pauli._phase.dtype).T + base_pauli._phase += 2 * np.logical_and(x, z).T.astype(base_pauli._phase.dtype) return base_pauli @@ -610,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 @@ -618,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 @@ -630,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 @@ -659,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).astype(base_pauli._phase.dtype).T + base_pauli._phase += 2 * np.logical_and(x1, x2).T.astype(base_pauli._phase.dtype) return base_pauli @@ -671,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).astype(base_pauli._phase.dtype).T + base_pauli._phase += x1 + 2 * np.logical_and(x1, x2).T.astype(base_pauli._phase.dtype) return base_pauli diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index 7d95511f5baa..3f529924bca9 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -419,7 +419,7 @@ def test_evolve_clifford2(self, gate, label): CZGate(), SwapGate(), ), - [int, np.int8, np.int16, np.int32, np.int64], + [int, np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64], ) ) @unpack diff --git a/test/python/quantum_info/operators/symplectic/test_pauli_list.py b/test/python/quantum_info/operators/symplectic/test_pauli_list.py index de7513719e8f..db38d428afc2 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -2049,8 +2049,17 @@ def test_phase_dtype_evolve_clifford(self): CZGate(), SwapGate(), ) - dtypes = [int, np.int8, np.int16, np.int32, np.int64] - evolved_dtypes = [] + 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)