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

Fix top-level switch statements in QuantumCircuit.compose #10164

Merged
merged 3 commits into from
May 30, 2023
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
41 changes: 28 additions & 13 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,9 @@ def compose(
lcr_1: 0 ═══════════ lcr_1: 0 ═══════════════════════

"""
# pylint: disable=cyclic-import
from qiskit.circuit.controlflow.switch_case import SwitchCaseOp

if inplace and front and self._control_flow_scopes:
# If we're composing onto ourselves while in a stateful control-flow builder context,
# there's no clear meaning to composition to the "front" of the circuit.
Expand Down Expand Up @@ -955,30 +958,42 @@ def compose(
)
edge_map.update(zip(other.clbits, dest.cbit_argument_conversion(clbits)))

# Cache for `map_register_to_dest`.
_map_register_cache = {}

def map_register_to_dest(theirs):
"""Map the target's registers to suitable equivalents in the destination, adding an
extra one if there's no exact match."""
if theirs.name in _map_register_cache:
return _map_register_cache[theirs.name]
mapped_bits = [edge_map[bit] for bit in theirs]
for ours in dest.cregs:
if mapped_bits == list(ours):
mapped_theirs = ours
break
else:
mapped_theirs = ClassicalRegister(bits=mapped_bits)
dest.add_register(mapped_theirs)
_map_register_cache[theirs.name] = mapped_theirs
return mapped_theirs

mapped_instrs: list[CircuitInstruction] = []
condition_register_map = {}
for instr in other.data:
n_qargs: list[Qubit] = [edge_map[qarg] for qarg in instr.qubits]
n_cargs: list[Clbit] = [edge_map[carg] for carg in instr.clbits]
n_op = instr.operation.copy()

# Map their registers over to ours, adding an extra one if there's no exact match.
if getattr(n_op, "condition", None) is not None:
target, value = n_op.condition
if isinstance(target, Clbit):
n_op.condition = (edge_map[target], value)
else:
if target.name not in condition_register_map:
mapped_bits = [edge_map[bit] for bit in target]
for our_creg in dest.cregs:
if mapped_bits == list(our_creg):
new_target = our_creg
break
else:
new_target = ClassicalRegister(bits=[edge_map[bit] for bit in target])
dest.add_register(new_target)
condition_register_map[target.name] = new_target
n_op.condition = (condition_register_map[target.name], value)
n_op.condition = (map_register_to_dest(target), value)
elif isinstance(n_op, SwitchCaseOp):
if isinstance(n_op.target, Clbit):
n_op.target = edge_map[n_op.target]
else:
n_op.target = map_register_to_dest(n_op.target)

mapped_instrs.append(CircuitInstruction(n_op, n_qargs, n_cargs))

Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/fix-compose-switch-19ada3828d939353.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
Fixed a bug in :meth:`.QuantumCircuit.compose` where the :attr:`.SwitchCaseOp.target` attribute
in the subcircuit would not get mapped to a register in the base circuit correctly.
41 changes: 41 additions & 0 deletions test/python/circuit/test_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
Parameter,
Gate,
Instruction,
CASE_DEFAULT,
SwitchCaseOp,
)
from qiskit.circuit.library import HGate, RZGate, CXGate, CCXGate, TwoLocal
from qiskit.test import QiskitTestCase
Expand Down Expand Up @@ -477,6 +479,45 @@ def test_compose_conditional_no_match(self):
self.assertEqual(z.condition[1], 1)
self.assertIs(x.condition[0][0], test.clbits[1])

def test_compose_switch_match(self):
"""Test that composition containing a `switch` with a register that matches proceeds
correctly."""
case_0 = QuantumCircuit(1, 2)
case_0.x(0)
case_1 = QuantumCircuit(1, 2)
case_1.z(0)
case_default = QuantumCircuit(1, 2)
cr = ClassicalRegister(2, "target")
right = QuantumCircuit(QuantumRegister(1), cr)
right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])

test = QuantumCircuit(QuantumRegister(3), cr, ClassicalRegister(2)).compose(
right, [1], [0, 1]
)

expected = test.copy_empty_like()
expected.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [1], [0, 1])
self.assertEqual(test, expected)

def test_compose_switch_no_match(self):
"""Test that composition containing a `switch` with a register that matches proceeds
correctly."""
case_0 = QuantumCircuit(1, 2)
case_0.x(0)
case_1 = QuantumCircuit(1, 2)
case_1.z(0)
case_default = QuantumCircuit(1, 2)
cr = ClassicalRegister(2, "target")
right = QuantumCircuit(QuantumRegister(1), cr)
right.switch(cr, [(0, case_0), (1, case_1), (CASE_DEFAULT, case_default)], [0], [0, 1])
test = QuantumCircuit(3, 3).compose(right, [1], [0, 1])

self.assertEqual(len(test.data), 1)
self.assertIsInstance(test.data[0].operation, SwitchCaseOp)
target = test.data[0].operation.target
self.assertIn(target, test.cregs)
self.assertEqual(list(target), test.clbits[0:2])

def test_compose_gate(self):
"""Composing with a gate.

Expand Down