Skip to content

Text circuit drawer fails when setting cregbundle=True on non-builder control flow #15822

@jakelishman

Description

@jakelishman

Environment

  • Qiskit version: 2.3.1
  • Python version: 3.13
  • Operating system: macOS

What is happening?

The text circuit drawer, QuantumCircuit.draw("text"), fails inside control-flow blocks that do not share the same clbits as the outer circuit when cregbundle=True. For circuits constructed using the control-flow builder interface, the bits are the same outside and inside the control-flow block, and no failure occurs.

Note that this is not a fault of the circuit construction - it is not a problem for the clbits used in the inner block of a control-flow op to be different objects to the outer circuit, because they are "bound" by the CircuitInstruction.clbits arguments that contain the instruction.

How can we reproduce the issue?

from qiskit import QuantumCircuit
from qiskit.circuit import Clbit, Qubit
from qiskit.circuit.classical import expr

cell = QuantumCircuit([Qubit(), Clbit()])
with cell.if_test(expr.lift(True)):
    cell.measure(0, 0)

qc = QuantumCircuit(2, 2)
for i in range(2):
    qc.compose(cell, qubits=[i], clbits=[i], inplace=True)
qc.draw("text", cregbundle=True)

gives

KeyError                                  Traceback (most recent call last)
File ~/code/qiskit/terra/qiskit/circuit/quantumcircuit.py:3733, in QuantumCircuit.find_bit(self, bit)
   3732 elif isinstance(bit, Clbit):
-> 3733     return self._data._clbit_indices[bit]
   3734 else:

KeyError: <Clbit uid=1>

The above exception was the direct cause of the following exception:

[... snip ...]

File ~/code/qiskit/terra/qiskit/visualization/circuit/text.py:1369, in TextDrawing.add_control_flow(self, node, layers, wire_map)
   1364 flow_wire_map.update(
   1365     {inner: wire_map[outer] for outer, inner in zip(node.qargs, circuit.qubits)}
   1366 )
   1367 for outer, inner in zip(node.cargs, circuit.clbits):
   1368     if self.cregbundle and (
-> 1369         (in_reg := get_bit_register(self._circuit, inner)) is not None
   1370     ):
   1371         out_reg = get_bit_register(self._circuit, outer)
   1372         flow_wire_map.update({in_reg: wire_map[out_reg]})

File ~/code/qiskit/terra/qiskit/visualization/circuit/_utils.py:218, in get_bit_register(circuit, bit)
    208 def get_bit_register(circuit, bit):
    209     """Get the register for a bit if there is one
    210
    211     Args:
   (...)
    216         ClassicalRegister: register associated with the bit
    217     """
--> 218     bit_loc = circuit.find_bit(bit)
    219     return bit_loc.registers[0][0] if bit_loc.registers else None

File ~/code/qiskit/terra/qiskit/circuit/quantumcircuit.py:3737, in QuantumCircuit.find_bit(self, bit)
   3735         raise CircuitError(f"Could not locate bit of unknown type: {type(bit)}")
   3736 except KeyError as err:
-> 3737     raise CircuitError(
   3738         f"Could not locate provided bit: {bit}. Has it been added to the QuantumCircuit?"
   3739     ) from err

CircuitError: 'Could not locate provided bit: <Clbit uid=1>. Has it been added to the QuantumCircuit?'

What should happen?

The circuit should draw correctly, and it should draw exactly as if constructed manually by the builder interface such as:

from qiskit import QuantumCircuit
from qiskit.circuit import Clbit, Qubit
from qiskit.circuit.classical import expr

cell = QuantumCircuit([Qubit(), Clbit()])

qc = QuantumCircuit(2, 2)
for i in range(2):
    with qc.if_test(expr.lift(True)):
        qc.measure(i, i)
qc.draw("text", cregbundle=True)
     ┌─────────── ┌─┐ ───────┐
q_0: ┤ If-0 true  ┤M├  End-0 ├───────────────────────────
     └─────────── └╥┘ ───────┘ ┌─────────── ┌─┐ ───────┐
q_1: ──────────────╫───────────┤ If-0 true  ┤M├  End-0 ├─
                   ║           └─────────── └╥┘ ───────┘
c: 2/══════════════╩═════════════════════════╩═══════════
                   0                         1

Any suggestions?

The trouble is somewhere in the creation of the flow_wire_map - it's never correct to try and look up a bit from an inner control-flow block directly in the context of the outer circuit, but that seems to be what we're doing. It's not immediately obvious to me how cregbundle works, though - a fix needs to make sure that the bundling is still behaving as expected.

Note that this is basically the same issue as #15823, but I made a separate issue because the mechanisms of the MPL and text drawers are often quite different, and it's not 100% guaranteed that something easy to fix in one will be easy to fix in the other.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingmod: visualizationqiskit.visualization

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions