diff --git a/qiskit/quantum_info/operators/symplectic/base_pauli.py b/qiskit/quantum_info/operators/symplectic/base_pauli.py index 28bb647d0364..e43eca4aff21 100644 --- a/qiskit/quantum_info/operators/symplectic/base_pauli.py +++ b/qiskit/quantum_info/operators/symplectic/base_pauli.py @@ -541,75 +541,50 @@ def _append_circuit(self, circuit, qargs=None): if qargs is None: qargs = list(range(self.num_qubits)) - if isinstance(circuit, QuantumCircuit): - gate = circuit.to_instruction() - else: + if not isinstance(circuit, QuantumCircuit): gate = circuit - # Basis Clifford Gates - basis_1q = { - "i": _evolve_i, - "id": _evolve_i, - "iden": _evolve_i, - "x": _evolve_x, - "y": _evolve_y, - "z": _evolve_z, - "h": _evolve_h, - "s": _evolve_s, - "sdg": _evolve_sdg, - "sinv": _evolve_sdg, - } - basis_2q = {"cx": _evolve_cx, "cz": _evolve_cz, "cy": _evolve_cy, "swap": _evolve_swap} - - # Non-Clifford gates - non_clifford = ["t", "tdg", "ccx", "ccz"] - - if isinstance(gate, str): - # Check if gate is a valid Clifford basis gate string - if gate not in basis_1q and gate not in basis_2q: - raise QiskitError(f"Invalid Clifford gate name string {gate}") - name = gate - else: - # Assume gate is an Instruction - name = gate.name - - # Apply gate if it is a Clifford basis gate - if name in non_clifford: - raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}") - if name in basis_1q: - if len(qargs) != 1: - raise QiskitError("Invalid qubits for 1-qubit gate.") - return basis_1q[name](self, qargs[0]) - if name in basis_2q: - if len(qargs) != 2: - raise QiskitError("Invalid qubits for 2-qubit gate.") - return basis_2q[name](self, qargs[0], qargs[1]) - - # If not a Clifford basis gate we try to unroll the gate and - # raise an exception if unrolling reaches a non-Clifford gate. - if gate.definition is None: - raise QiskitError(f"Cannot apply Instruction: {gate.name}") - if not isinstance(gate.definition, QuantumCircuit): - raise QiskitError( - "{} instruction definition is {}; expected QuantumCircuit".format( - gate.name, type(gate.definition) + if isinstance(gate, str): + # Check if gate is a valid Clifford basis gate string + if gate not in _basis_1q and gate not in _basis_2q: + raise QiskitError(f"Invalid Clifford gate name string {gate}") + name = gate + else: + # Assume gate is an Instruction + name = gate.name + + # Apply gate if it is a Clifford basis gate + if name in _non_clifford: + raise QiskitError(f"Cannot update Pauli with non-Clifford gate {name}") + if name in _basis_1q: + if len(qargs) != 1: + raise QiskitError("Invalid qubits for 1-qubit gate.") + return _basis_1q[name](self, qargs[0]) + if name in _basis_2q: + if len(qargs) != 2: + raise QiskitError("Invalid qubits for 2-qubit gate.") + return _basis_2q[name](self, qargs[0], qargs[1]) + + # If not a Clifford basis gate we try to unroll the gate and + # raise an exception if unrolling reaches a non-Clifford gate. + if gate.definition is None: + raise QiskitError(f"Cannot apply Instruction: {gate.name}") + if not isinstance(gate.definition, QuantumCircuit): + raise QiskitError( + "{} instruction definition is {}; expected QuantumCircuit".format( + gate.name, type(gate.definition) + ) ) - ) - flat_instr = gate.definition - bit_indices = { - bit: index - for bits in [flat_instr.qubits, flat_instr.clbits] - for index, bit in enumerate(bits) - } + circuit = gate.definition - for instruction in flat_instr: + for instruction in circuit: if instruction.clbits: raise QiskitError( f"Cannot apply Instruction with classical bits: {instruction.operation.name}" ) # Get the integer position of the flat register - new_qubits = [qargs[bit_indices[tup]] for tup in instruction.qubits] + new_qubits = [qargs[circuit.find_bit(qb)[0]] for qb in instruction.qubits] self._append_circuit(instruction.operation, new_qubits) # Since the individual gate evolution functions don't take mod @@ -715,6 +690,42 @@ def _evolve_swap(base_pauli, q1, q2): return base_pauli +def _evolve_ecr(base_pauli, q1, q2): + """Update P -> ECR.P.ECR""" + base_pauli = _evolve_s(base_pauli, q1) + base_pauli = _evolve_h(base_pauli, q2) + base_pauli = _evolve_s(base_pauli, q2) + base_pauli = _evolve_h(base_pauli, q2) + base_pauli = _evolve_cx(base_pauli, q1, q2) + base_pauli = _evolve_x(base_pauli, q1) + return base_pauli + + def _count_y(x, z, dtype=None): """Count the number of I Paulis""" return (x & z).sum(axis=1, dtype=dtype) + + +# Basis Clifford Gates +_basis_1q = { + "i": _evolve_i, + "id": _evolve_i, + "iden": _evolve_i, + "x": _evolve_x, + "y": _evolve_y, + "z": _evolve_z, + "h": _evolve_h, + "s": _evolve_s, + "sdg": _evolve_sdg, + "sinv": _evolve_sdg, +} +_basis_2q = { + "cx": _evolve_cx, + "cz": _evolve_cz, + "cy": _evolve_cy, + "swap": _evolve_swap, + "ecr": _evolve_ecr, +} + +# Non-Clifford gates +_non_clifford = ["t", "tdg", "ccx", "ccz"] diff --git a/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml new file mode 100644 index 000000000000..143dfadf31e2 --- /dev/null +++ b/releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + :meth:`.Pauli.evolve` now correctly handles quantum circuits containing ECR gates. Formerly they were not recognized as Clifford gates, and an error was raised. + - | + Fixed a bug in :meth:`.Pauli.evolve` where evolving by a circuit with a name matching certain Clifford gates ('cx', 'cz', etc) would evolve the Pauli according to the name of the circuit, not by the contents of the circuit. This bug occurred only with the non-default option ``frame='s'``. diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index c760407ad3fa..875dd9237810 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -36,6 +36,7 @@ CZGate, CYGate, SwapGate, + ECRGate, EfficientSU2, ) from qiskit.circuit.library.generalized_gates import PauliGate @@ -410,7 +411,11 @@ def test_evolve_clifford1(self, gate, label): self.assertEqual(value, value_h) self.assertEqual(value_inv, value_s) - @data(*it.product((CXGate(), CYGate(), CZGate(), SwapGate()), pauli_group_labels(2, False))) + @data( + *it.product( + (CXGate(), CYGate(), CZGate(), SwapGate(), ECRGate()), pauli_group_labels(2, False) + ) + ) @unpack def test_evolve_clifford2(self, gate, label): """Test evolve method for 2-qubit Clifford gates.""" @@ -439,6 +444,7 @@ def test_evolve_clifford2(self, gate, label): CYGate(), CZGate(), SwapGate(), + ECRGate(), ), [int, np.int8, np.uint8, np.int16, np.uint16, np.int32, np.uint32, np.int64, np.uint64], ) @@ -468,6 +474,13 @@ def test_evolve_clifford_qargs(self): self.assertEqual(value, value_h) self.assertEqual(value_inv, value_s) + @data("s", "h") + def test_evolve_with_misleading_name(self, frame): + """Test evolve by circuit contents, not by name (fixed bug).""" + circ = QuantumCircuit(2, name="cx") + p = Pauli("IX") + self.assertEqual(p, p.evolve(circ, frame=frame)) + def test_barrier_delay_sim(self): """Test barrier and delay instructions can be simulated""" target_circ = QuantumCircuit(2) 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 7ab6feaefe98..0ef7079f461a 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli_list.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli_list.py @@ -33,6 +33,7 @@ XGate, YGate, ZGate, + ECRGate, ) from qiskit.quantum_info.operators import ( Clifford, @@ -1997,10 +1998,12 @@ def test_evolve_clifford1(self, gate): CYGate(), CZGate(), SwapGate(), + ECRGate(), Clifford(CXGate()), Clifford(CYGate()), Clifford(CZGate()), Clifford(SwapGate()), + Clifford(ECRGate()), ) ) def test_evolve_clifford2(self, gate): @@ -2033,6 +2036,7 @@ def test_phase_dtype_evolve_clifford(self): CYGate(), CZGate(), SwapGate(), + ECRGate(), ) dtypes = [ int,