Skip to content

Commit

Permalink
Add Expr supprt to DAGCircuit simple constructors
Browse files Browse the repository at this point in the history
This adds support to the `DAGCircuit` builder methods
`apply_operation_front` and `apply_operation_back`, and consequently to
the converter `circuit_to_dag`.  This commit does not directly enhance
`DAGCircuit.compose`, which will come later.

The principal change is that `_bits_in_condition` is modified to the
more general `_bits_in_operation`, which now accounts for both a
`condition` (if set) and any additional fields from a `target` if the
operation is a suitable `ControlFlowOp`.  This has the effect of
upgrading the converters into the `SabreDAG` format as well, but nothing
is done to change the Sabre algorithms themselves.
  • Loading branch information
jakelishman committed Jul 3, 2023
1 parent d0a2bea commit dce6cdd
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 38 deletions.
35 changes: 19 additions & 16 deletions qiskit/dagcircuit/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import rustworkx as rx

from qiskit.circuit import ControlFlowOp, ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp
from qiskit.circuit.controlflow import condition_resources
from qiskit.circuit.controlflow import condition_resources, node_resources
from qiskit.circuit.quantumregister import QuantumRegister, Qubit
from qiskit.circuit.classicalregister import ClassicalRegister, Clbit
from qiskit.circuit.gate import Gate
Expand Down Expand Up @@ -476,17 +476,26 @@ def _check_bits(self, args, amap):
raise DAGCircuitError(f"(qu)bit {wire} not found in {amap}")

@staticmethod
def _bits_in_condition(cond):
"""Return a list of bits in the given condition.
def _bits_in_operation(operation):
"""Return an iterable over the classical bits that are inherent to an instruction. This
includes a `condition`, or the `target` of a :class:`.ControlFlowOp`.
Args:
cond (tuple or expr.Expr or None): optional condition in any form that the control-flow
operations accept.
instruction: the :class:`~.circuit.Instruction` instance for a node.
Returns:
list[Clbit]: list of classical bits
Iterable[Clbit]: the :class:`.Clbit`\\ s involved.
"""
return [] if cond is None else list(condition_resources(cond).clbits)
if (condition := getattr(operation, "condition", None)) is not None:
yield from condition_resources(condition).clbits
if isinstance(operation, SwitchCaseOp):
target = operation.target
if isinstance(target, Clbit):
yield target
elif isinstance(target, ClassicalRegister):
yield from target
else:
yield from node_resources(target).clbits

def _increment_op(self, op):
if op.name in self._op_names:
Expand Down Expand Up @@ -571,8 +580,7 @@ def apply_operation_back(self, op, qargs=(), cargs=()):
qargs = tuple(qargs) if qargs is not None else ()
cargs = tuple(cargs) if cargs is not None else ()

all_cbits = self._bits_in_condition(getattr(op, "condition", None))
all_cbits = set(all_cbits).union(cargs)
all_cbits = set(self._bits_in_operation(op)).union(cargs)

self._check_condition(op.name, getattr(op, "condition", None))
self._check_bits(qargs, self.output_map)
Expand Down Expand Up @@ -603,8 +611,7 @@ def apply_operation_front(self, op, qargs=(), cargs=()):
Raises:
DAGCircuitError: if initial nodes connected to multiple out edges
"""
all_cbits = self._bits_in_condition(getattr(op, "condition", None))
all_cbits.extend(cargs)
all_cbits = set(self._bits_in_operation(op)).union(cargs)

self._check_condition(op.name, getattr(op, "condition", None))
self._check_bits(qargs, self.input_map)
Expand Down Expand Up @@ -1183,9 +1190,7 @@ def substitute_node_with_dag(self, node, input_dag, wires=None, propagate_condit
# condition as well.
if not propagate_condition:
node_wire_order += [
bit
for bit in self._bits_in_condition(getattr(node.op, "condition", None))
if bit not in node_cargs
bit for bit in self._bits_in_operation(node.op) if bit not in node_cargs
]
if len(wires) != len(node_wire_order):
raise DAGCircuitError(
Expand Down Expand Up @@ -1729,8 +1734,6 @@ def serial_layers(self):
op = copy.copy(next_node.op)
qargs = copy.copy(next_node.qargs)
cargs = copy.copy(next_node.cargs)
condition = copy.copy(getattr(next_node.op, "condition", None))
_ = self._bits_in_condition(condition)

# Add node to new_layer
new_layer.apply_operation_back(op, qargs, cargs)
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def _inner_run(self, dag, coupling_map):
for node in dag.topological_op_nodes():
cargs = {original_clbit_indices[x] for x in node.cargs}
if node.op.condition is not None:
for clbit in dag._bits_in_condition(node.op.condition):
for clbit in dag._bits_in_operation(node.op):
cargs.add(original_clbit_indices[clbit])

dag_list.append(
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def run(self, dag):
for node in dag.topological_op_nodes():
cargs = {self._clbit_indices[x] for x in node.cargs}
if node.op.condition is not None:
for clbit in dag._bits_in_condition(node.op.condition):
for clbit in dag._bits_in_operation(node.op):
cargs.add(self._clbit_indices[clbit])

dag_list.append(
Expand Down
55 changes: 54 additions & 1 deletion test/python/converters/test_circuit_to_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

import unittest

from qiskit.circuit import QuantumRegister, ClassicalRegister, QuantumCircuit, Clbit
from qiskit.circuit.classical import expr
from qiskit.converters import dag_to_circuit, circuit_to_dag
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit.test import QiskitTestCase


Expand Down Expand Up @@ -51,6 +52,58 @@ def test_calibrations(self):
circuit_out = dag_to_circuit(dag)
self.assertEqual(len(circuit_out.calibrations), 1)

def test_wires_from_expr_nodes_condition(self):
"""Test that the classical wires implied by an `Expr` node in a control-flow op's
`condition` are correctly transferred."""
# The control-flow builder interface always includes any classical wires in the blocks of
# the operation, so we test by using manually constructed blocks that don't do that. It's
# not required by the `QuantumCircuit` model (just like `c_if` instructions don't expand
# their `cargs`).
inner = QuantumCircuit(1)
inner.x(0)
cr1 = ClassicalRegister(2, "a")
cr2 = ClassicalRegister(2, "b")
clbit = Clbit()
outer = QuantumCircuit(QuantumRegister(1), cr1, cr2, [clbit])
# Note that 'cr2' is not in the condition.
outer.if_test(expr.logic_and(expr.equal(cr1, 3), expr.logic_not(clbit)), inner, [0], [])
outer.while_loop(expr.logic_or(expr.less(2, cr1), clbit), inner, [0], [])

dag = circuit_to_dag(outer)
expected_wires = set(outer.qubits) | set(cr1) | {clbit}
for node in dag.topological_op_nodes():
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.condition, expr.Expr)
self.assertEqual(test_wires, expected_wires)

roundtripped = dag_to_circuit(dag)
for original, test in zip(outer, roundtripped):
self.assertEqual(original.operation.condition, test.operation.condition)

def test_wires_from_expr_nodes_target(self):
"""Test that the classical wires implied by an `Expr` node in a control-flow op's
`target` are correctly transferred."""
case_1 = QuantumCircuit(1)
case_1.x(0)
case_2 = QuantumCircuit(1)
case_2.y(0)
cr1 = ClassicalRegister(2, "a")
cr2 = ClassicalRegister(2, "b")
outer = QuantumCircuit(QuantumRegister(1), cr1, cr2)
# Note that 'cr2' is not in the condition.
outer.switch(expr.bit_and(cr1, 2), [(1, case_1), (2, case_2)], [0], [])

dag = circuit_to_dag(outer)
expected_wires = set(outer.qubits) | set(cr1)
for node in dag.topological_op_nodes():
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.target, expr.Expr)
self.assertEqual(test_wires, expected_wires)

roundtripped = dag_to_circuit(dag)
for original, test in zip(outer, roundtripped):
self.assertEqual(original.operation.target, test.operation.target)


if __name__ == "__main__":
unittest.main(verbosity=2)
101 changes: 82 additions & 19 deletions test/python/dagcircuit/test_dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,26 @@
import rustworkx as rx
from numpy import pi

from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGInNode, DAGOutNode
from qiskit.circuit import QuantumRegister
from qiskit.circuit import ClassicalRegister, Clbit
from qiskit.circuit import QuantumCircuit, Qubit
from qiskit.circuit import Measure
from qiskit.circuit import Reset
from qiskit.circuit import Delay
from qiskit.circuit import Gate, Instruction
from qiskit.circuit import Parameter
from qiskit.circuit.library.standard_gates.i import IGate
from qiskit.circuit.library.standard_gates.h import HGate
from qiskit.circuit.library.standard_gates.x import CXGate
from qiskit.circuit.library.standard_gates.z import CZGate
from qiskit.circuit.library.standard_gates.x import XGate
from qiskit.circuit.library.standard_gates.y import YGate
from qiskit.circuit.library.standard_gates.u1 import U1Gate
from qiskit.circuit.library.standard_gates.rx import RXGate
from qiskit.circuit.barrier import Barrier
from qiskit.dagcircuit.exceptions import DAGCircuitError
from qiskit.dagcircuit import DAGCircuit, DAGOpNode, DAGInNode, DAGOutNode, DAGCircuitError
from qiskit.circuit import (
QuantumCircuit,
QuantumRegister,
ClassicalRegister,
Clbit,
Qubit,
Measure,
Delay,
Reset,
Gate,
Instruction,
Parameter,
Barrier,
SwitchCaseOp,
IfElseOp,
WhileLoopOp,
)
from qiskit.circuit.classical import expr
from qiskit.circuit.library import IGate, HGate, CXGate, CZGate, XGate, YGate, U1Gate, RXGate
from qiskit.converters import circuit_to_dag
from qiskit.test import QiskitTestCase

Expand Down Expand Up @@ -580,6 +581,68 @@ def test_apply_operation_front(self):

self.assertIn(reset_node, set(self.dag.predecessors(h_node)))

def test_apply_operation_expr_condition(self):
"""Test that the operation-applying functions correctly handle wires implied from `Expr`
nodes in the `condition` field of `ControlFlowOp` instances."""
inner = QuantumCircuit(1)
inner.x(0)

qr = QuantumRegister(1)
cr1 = ClassicalRegister(2, "a")
cr2 = ClassicalRegister(2, "b")
clbit = Clbit()

dag = DAGCircuit()
dag.add_qreg(qr)
dag.add_creg(cr1)
dag.add_creg(cr2)
dag.add_clbits([clbit])

# Note that 'cr2' is not in either condition.
expected_wires = set(qr) | set(cr1) | {clbit}

op = IfElseOp(expr.logic_and(expr.equal(cr1, 3), expr.logic_not(clbit)), inner, None)
node = dag.apply_operation_back(op, qr, ())
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.condition, expr.Expr)
self.assertEqual(test_wires, expected_wires)

op = WhileLoopOp(expr.logic_or(expr.less(2, cr1), clbit), inner)
node = dag.apply_operation_front(op, qr, ())
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.condition, expr.Expr)
self.assertEqual(test_wires, expected_wires)

def test_apply_operation_expr_target(self):
"""Test that the operation-applying functions correctly handle wires implied from `Expr`
nodes in the `target` field of `SwitchCaseOp`."""
case_1 = QuantumCircuit(1)
case_1.x(0)
case_2 = QuantumCircuit(1)
case_2.y(0)
qr = QuantumRegister(1)
cr1 = ClassicalRegister(2, "a")
cr2 = ClassicalRegister(2, "b")
# Note that 'cr2' is not in the condition.
op = SwitchCaseOp(expr.bit_and(cr1, 2), [(1, case_1), (2, case_2)])

expected_wires = set(qr) | set(cr1)

dag = DAGCircuit()
dag.add_qreg(qr)
dag.add_creg(cr1)
dag.add_creg(cr2)

node = dag.apply_operation_back(op, qr, ())
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.target, expr.Expr)
self.assertEqual(test_wires, expected_wires)

node = dag.apply_operation_front(op, qr, ())
test_wires = {wire for _source, _dest, wire in dag.edges(node)}
self.assertIsInstance(node.op.target, expr.Expr)
self.assertEqual(test_wires, expected_wires)


class TestDagNodeSelection(QiskitTestCase):
"""Test methods that select certain dag nodes"""
Expand Down

0 comments on commit dce6cdd

Please sign in to comment.