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

Instructions from `QuantumCircuit.to_instruction` with control flow cannot be used in larger circuits

chriseclectic opened this issue · comments

Environment

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

What is happening?

Using to_instruction on a circuit with control flow will flatten its registers. If this instruction definition is then composed into another circuit it will give errors like what are shown in previous linked issues. If the instruction is composed without using its definition, then it will appear to work until any unrolling happens.

Related issues #12083 #12084

How can we reproduce the issue?

Consider the following basic teleport circuit

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)
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)
qc.measure(2, cr)

If I convert to an instruction and compose its definition

inst = qc.to_instruction()
tmp = QuantumCircuit(inst.num_qubits, inst.num_clbits)
tmp.compose(inst.definition, range(inst.num_qubits), range(inst.num_clbits), inplace=True)

I get the error

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[45], line 23
     20 qc.measure(2, cr)
     22 qc = QuantumCircuit(inst.num_qubits, inst.num_clbits)
---> 23 qc.compose(inst.definition, range(inst.num_qubits), range(inst.num_clbits), inplace=True)

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/quantumcircuit.py:1009, in QuantumCircuit.compose(self, other, qubits, clbits, front, inplace, wrap)
   1007 mapped_instrs: CircuitData = other._data.copy()
   1008 mapped_instrs.replace_bits(qubits=mapped_qubits, clbits=mapped_clbits)
-> 1009 mapped_instrs.map_ops(map_vars)
   1011 append_existing = None
   1012 if front:

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/quantumcircuit.py:1004, in QuantumCircuit.compose.<locals>.map_vars(op)
   1002     n_op.condition = variable_mapper.map_condition(condition)
   1003 if isinstance(n_op, SwitchCaseOp):
-> 1004     n_op.target = variable_mapper.map_target(n_op.target)
   1005 return n_op

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/_classical_resource_map.py:118, in VariableMapper.map_target(self, target)
    116     return self.bit_map[target]
    117 if isinstance(target, ClassicalRegister):
--> 118     return self._map_register(target)
    119 return self.map_expr(target)

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/_classical_resource_map.py:58, in VariableMapper._map_register(self, theirs)
     56 if (mapped_theirs := self.register_map.get(theirs.name)) is not None:
     57     return mapped_theirs
---> 58 mapped_bits = [self.bit_map[bit] for bit in theirs]
     59 for ours in self.target_cregs:
     60     if mapped_bits == list(ours):

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/_classical_resource_map.py:58, in <listcomp>(.0)
     56 if (mapped_theirs := self.register_map.get(theirs.name)) is not None:
     57     return mapped_theirs
---> 58 mapped_bits = [self.bit_map[bit] for bit in theirs]
     59 for ours in self.target_cregs:
     60     if mapped_bits == list(ours):

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

If I compose as an instruction it appears to work until transpilation/decomposition happens and then a similar error to the above will be raised

inst = qc.to_instruction()
tmp = QuantumCircuit(inst.num_qubits, inst.num_clbits)
tmp.compose(inst, range(inst.num_qubits), range(inst.num_clbits), inplace=True)
tmp.decompose()

inst = qc.to_instruction()
qc = QuantumCircuit(inst.num_qubits, inst.num_clbits)
qc.append(inst, range(inst.num_qubits), range(inst.num_clbits))
qc.decompose()
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[52], line 25
     23 qc = QuantumCircuit(inst.num_qubits, inst.num_clbits)
     24 qc.append(inst, range(inst.num_qubits), range(inst.num_clbits))
---> 25 qc.decompose()

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/circuit/quantumcircuit.py:1651, in QuantumCircuit.decompose(self, gates_to_decompose, reps)
   1649 pass_ = Decompose(gates_to_decompose)
   1650 for _ in range(reps):
-> 1651     dag = pass_.run(dag)
   1652 # do not copy operations, this is done in the conversion with circuit_to_dag
   1653 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:92, in circuit_to_dag(circuit, copy_operations, qubit_order, clbit_order)
     90     if copy_operations:
     91         op = copy.deepcopy(op)
---> 92     dagcircuit.apply_operation_back(op, instruction.qubits, instruction.clbits, check=False)
     94 dagcircuit.duration = circuit.duration
     95 dagcircuit.unit = circuit.unit

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/dagcircuit/dagcircuit.py:682, in DAGCircuit.apply_operation_back(self, op, qargs, cargs, check)
    675 self._increment_op(op)
    677 # Add new in-edges from predecessors of the output nodes to the
    678 # operation node while deleting the old in-edges of the output nodes
    679 # and adding new edges from the operation node to each output node
    680 self._multi_graph.insert_node_on_in_edges_multiple(
    681     node._node_id,
--> 682     [self.output_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
    683 )
    684 return node

File ~/mambaforge/envs/qiskit1/lib/python3.11/site-packages/qiskit/dagcircuit/dagcircuit.py:682, in <listcomp>(.0)
    675 self._increment_op(op)
    677 # Add new in-edges from predecessors of the output nodes to the
    678 # operation node while deleting the old in-edges of the output nodes
    679 # and adding new edges from the operation node to each output node
    680 self._multi_graph.insert_node_on_in_edges_multiple(
    681     node._node_id,
--> 682     [self.output_map[bit]._node_id for bits in (qargs, all_cbits) for bit in bits],
    683 )
    684 return node

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

What should happen?

Circuit with classical registers and control flow ops should be able to be converted to instructions and used as blocks in larger circuits.

Any suggestions?

This likely is the same underlying issue as #12084, since to_instruction is essentially flattening the circuit for its definition in the same way I attempted to flatten the circuit manually in #12084.

Yeah, this is exactly the same underlying issue as #12084, because QuantumCircuit.append just calls to_instruction internally (imo it's not a great part of the interface that we do that implicitly - it makes it super hard to understand what's going on).

Let me close this one as duplicate, but I'll comment on the other one with the current state of things and try to chart a path forwards - sorry for the delay.

Duplicate of #12084