Skip to content

Commit

Permalink
Fix top-level switch statements in QuantumCircuit.compose (#10164)
Browse files Browse the repository at this point in the history
* Fix top-level `switch` statements in `QuantumCircuit.compose`

The register-mapping code was not being applied to `SwitchCaseOp.target`
in the same way that it is for conditions.  This commit does not change
any behaviour about recursing into _nested_ control-flow blocks, which
still likely have problems with composition.

* Apply suggestions from review

(cherry picked from commit 76850e1)
  • Loading branch information
jakelishman authored and mergify[bot] committed May 30, 2023
1 parent 5f39f6b commit c9e4291
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 13 deletions.
41 changes: 28 additions & 13 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,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 @@ -956,30 +959,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

0 comments on commit c9e4291

Please sign in to comment.