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

Support ECR gates in Pauli.evolve(QuantumCircuit) #12095

Merged
merged 10 commits into from
Apr 18, 2024
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
Loading