Skip to content

Commit

Permalink
Support ECR gates in Pauli.evolve(QuantumCircuit) (Qiskit#12095)
Browse files Browse the repository at this point in the history
* add _evolve_ecr()

- Adds support for Pauli (and related classes) to evolve through ECR gates encountered in a quantum circuit.
- Also moved the dicts of special-case gates (`basis_1q`, `basis_2q`, `non-clifford`) outside the subroutine definition. They are now just after the `_evolve_*()` functions they reference.

* fix pauli.evolve bug for certain circuit names

- Should fix qiskit issue Qiskit#12093
- Bug happened after converting circuit to instruction, which AFAICT was not necessary. Now if input is a QuantumCircuit, that part of the code is bypassed.
- Removed creation of a look-up dict of bit locations, since `QuantumCircuit.find_bit` already provides one.

* add ECRGate to `evolve()` tests

* mark gate-dicts as private

* add test evolving by circuit named 'cx'

Test showing issue Qiskit#12093 is solved.

* add release note for pauli-evolve fixes

* Update test_pauli_list.py
  • Loading branch information
aeddins-ibm authored Apr 18, 2024
1 parent 437837e commit d10f9a0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 60 deletions.
129 changes: 70 additions & 59 deletions qiskit/quantum_info/operators/symplectic/base_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"]
6 changes: 6 additions & 0 deletions releasenotes/notes/fix-pauli-evolve-ecr-and-name-bugs.yaml
Original file line number Diff line number Diff line change
@@ -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'``.
15 changes: 14 additions & 1 deletion test/python/quantum_info/operators/symplectic/test_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
CZGate,
CYGate,
SwapGate,
ECRGate,
EfficientSU2,
)
from qiskit.circuit.library.generalized_gates import PauliGate
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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],
)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
XGate,
YGate,
ZGate,
ECRGate,
)
from qiskit.quantum_info.operators import (
Clifford,
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -2033,6 +2036,7 @@ def test_phase_dtype_evolve_clifford(self):
CYGate(),
CZGate(),
SwapGate(),
ECRGate(),
)
dtypes = [
int,
Expand Down

0 comments on commit d10f9a0

Please sign in to comment.