Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Expr supprt to DAGCircuit simple constructors #10362

Merged
merged 1 commit into from
Jul 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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