`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