Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.

Home Page:https://www.ibm.com/quantum/qiskit

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`BlockCollapser.collapse_to_operation` fails with `if_else` and `switch_case` ops

chriseclectic opened this issue · comments

Environment

  • Qiskit version: 1.1
  • Python version: 3.11
  • Operating system: MacOS

What is happening?

Related issues #12084

A concrete example of the above issue arises when trying to use the CollectAndCollapse transpiler pass (or BlockCollector and BlockCollapser directly) when collecting circuits with control flow ops in them. In particular the API for BlockCollapser only allows collapsing a block into an Instruction which attempts to flatten classical registers in its body definition leading to the errors in 12084.

How can we reproduce the issue?

Here is an example using a teleportation circuit done 3 different ways: using c_if, using if_else, and using switch case. c_if works, but if_else and switch both fail (for different reasons).

from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister, Parameter
from qiskit.converters import dag_to_circuit, circuit_to_dag
from qiskit.dagcircuit.collect_blocks import BlockCollector, BlockCollapser

def teleport_circuit(condition: str):
    qr = QuantumRegister(3, "q")
    cr = ClassicalRegister(1, "c")
    cr_aux = ClassicalRegister(2, "aux")
    par = Parameter("a")
    qc = QuantumCircuit(qr, cr, cr_aux)
    qc.ry(par, 0)
    qc.h(1)
    qc.cx(1, 2)
    qc.cx(0, 1)
    qc.h(0)
    # Measure and conditionals
    if condition == "c_if":
        qc.measure(0, cr_aux[0])
        qc.z(2).c_if(cr_aux[0], 1)
        qc.measure(1, cr_aux[1])
        qc.x(2).c_if(cr_aux[1], 1)
    elif condition == "if_else":
        qc.measure(0, cr_aux[0])
        with qc.if_test((cr_aux[0], 1)):
            qc.z(2)
        qc.measure(1, cr_aux[1])
        with qc.if_test((cr_aux[1], 1)):
            qc.x(2)
    elif condition == "switch":
        qc.measure([0, 1], cr_aux)
        with qc.switch(cr_aux) as case:
            with case(1):
                qc.z(2)
            with case(2):
                qc.x(2)
            with case(3):
                qc.z(2)
                qc.x(2)
    # final measure
    qc.measure(2, cr)
    return qc


def test_block_collapser(condition):
    circ = teleport_circuit(condition)
    dag = circuit_to_dag(circ)
    blocks = BlockCollector(dag).collect_all_matching_blocks(lambda _: True)
    BlockCollapser(dag).collapse_to_operation(blocks, lambda circuit: circuit.to_instruction())
    new_circ = dag_to_circuit(dag)
    new_circ.decompose()
    return new_circ

For if_else we get the error:

---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
Cell In[14], line 1
----> 1 test_block_collapser("if_else")

Cell In[12], line 50, in test_block_collapser(condition)
     48 dag = circuit_to_dag(circ)
     49 blocks = BlockCollector(dag).collect_all_matching_blocks(lambda _: True)
---> 50 BlockCollapser(dag).collapse_to_operation(blocks, lambda circuit: circuit.to_instruction())
     51 new_circ = dag_to_circuit(dag)
     52 new_circ.decompose()

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/dagcircuit/collect_blocks.py:383, in BlockCollapser.collapse_to_operation(self, blocks, collapse_fn)
    381     cond = getattr(node.op, "condition", None)
    382     if cond is not None:
--> 383         instructions.c_if(*cond)
    385 # Collapse this quantum circuit into an operation.
    386 op = collapse_fn(qc)

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/instructionset.py:154, in InstructionSet.c_if(self, classical, val)
    151         data, idx = instruction
    152         instruction = data[idx]
    153         data[idx] = instruction.replace(
--> 154             operation=instruction.operation.c_if(classical, val)
    155         )
    156 return self

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/controlflow/if_else.py:158, in IfElseOp.c_if(self, classical, val)
    157 def c_if(self, classical, val):
--> 158     raise NotImplementedError(
    159         "IfElseOp cannot be classically controlled through Instruction.c_if. "
    160         "Please nest it in an IfElseOp instead."
    161     )

NotImplementedError: IfElseOp cannot be classically controlled through Instruction.c_if. Please nest it in an IfElseOp instead.

For switch block collection appears to work, but we get a delayed error when .decompose() is called

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[15], line 1
----> 1 test_block_collapser("switch")

Cell In[12], line 52, in test_block_collapser(condition)
     50 BlockCollapser(dag).collapse_to_operation(blocks, lambda circuit: circuit.to_instruction())
     51 new_circ = dag_to_circuit(dag)
---> 52 new_circ.decompose()
     53 return new_circ

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/quantumcircuit.py:3114, in QuantumCircuit.decompose(self, gates_to_decompose, reps)
   3112 pass_ = Decompose(gates_to_decompose)
   3113 for _ in range(reps):
-> 3114     dag = pass_.run(dag)
   3115 # do not copy operations, this is done in the conversion with circuit_to_dag
   3116 return dag_to_circuit(dag, copy_operations=False)

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/transpiler/passes/basis/decompose.py:64, in Decompose.run(self, dag)
     62             dag.substitute_node(node, rule[0].operation, inplace=True)
     63         else:
---> 64             decomposition = circuit_to_dag(node.op.definition)
     65             dag.substitute_node_with_dag(node, decomposition)
     67 return dag

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/converters/circuit_to_dag.py:99, in circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order)
     97     if copy_operations:
     98         op = copy.deepcopy(op)
---> 99     dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits, check=False)
    101 dagcircuit.duration = circuit.duration
    102 dagcircuit.unit = circuit.unit

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/dagcircuit/dagcircuit.py:769, in DAGCircuit.apply_operation_back(self, op, qargs, cargs, check)
    762 self._increment_op(op)
    764 # Add new in-edges from predecessors of the output nodes to the
    765 # operation node while deleting the old in-edges of the output nodes
    766 # and adding new edges from the operation node to each output node
    767 self._multi_graph.insert_node_on_in_edges_multiple(
    768     node._node_id,
--> 769     [self.output_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
    770 )
    771 return node

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/dagcircuit/dagcircuit.py:769, in <listcomp>(.0)
    762 self._increment_op(op)
    764 # Add new in-edges from predecessors of the output nodes to the
    765 # operation node while deleting the old in-edges of the output nodes
    766 # and adding new edges from the operation node to each output node
    767 self._multi_graph.insert_node_on_in_edges_multiple(
    768     node._node_id,
--> 769     [self.output_map[bit]._node_id for bits in (qargs, cargs, additional) for bit in bits],
    770 )
    771 return node

KeyError: Clbit(ClassicalRegister(2, 'aux'), 0)

What should happen?

From discussions with @jakelishman it sounds like these cases shouldn't be supported in Qiskit yet, however the block collector should probably validate and raise a more informative error for these cases. In general I think the block collapse should support being able to collect control flow operations, and if that is not possible using the current collapsing to operation then maybe we need an alternative API for these sort of collection routines?

Any suggestions?

No response