Skip to content

Commit

Permalink
Add Expr support to the control-flow builders
Browse files Browse the repository at this point in the history
This is generally relatively straightforwards; anywhere where we examine
the resources used by an operation, we need to update to account for
classical resources potentially being tied up in `ControlFlowOp` fields
in `Expr` nodes as well.

This also has the side effect of fixing a bug where a nested
`SwitchCaseOp.target` wasn't considered in the scope building for _all_
control-flow operations, which could incorrectly cause some registers to
be missed in outer scopes.
  • Loading branch information
jakelishman committed Jul 6, 2023
1 parent 0abfb1f commit cf2170b
Show file tree
Hide file tree
Showing 6 changed files with 421 additions and 30 deletions.
18 changes: 15 additions & 3 deletions qiskit/circuit/controlflow/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from qiskit.circuit.quantumregister import Qubit, QuantumRegister
from qiskit.circuit.register import Register

from ._builder_utils import condition_resources
from ._builder_utils import condition_resources, node_resources

if typing.TYPE_CHECKING:
import qiskit # pylint: disable=cyclic-import
Expand Down Expand Up @@ -154,7 +154,7 @@ def _copy_mutable_properties(self, instruction: Instruction) -> Instruction:
changes to this class.
"""
# In general the tuple creation should be a no-op, because ``tuple(t) is t`` for tuples.
instruction.condition = None if self.condition is None else tuple(self.condition)
instruction.condition = self.condition
return instruction

# Provide some better error messages, just in case something goes wrong during development and
Expand Down Expand Up @@ -402,7 +402,7 @@ def build(
and using the minimal set of resources necessary to support them, within the enclosing
scope.
"""
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import QuantumCircuit, SwitchCaseOp

# There's actually no real problem with building a scope more than once. This flag is more
# so _other_ operations, which aren't safe can be forbidden, such as mutating instructions
Expand Down Expand Up @@ -451,6 +451,18 @@ def build(
if register not in self.registers:
self.add_register(register)
out.add_register(register)
elif isinstance(instruction.operation, SwitchCaseOp):
target = instruction.operation.target
if isinstance(target, Clbit):
target_registers = ()
elif isinstance(target, ClassicalRegister):
target_registers = (target,)
else:
target_registers = node_resources(target).cregs
for register in target_registers:
if register not in self.registers:
self.add_register(register)
out.add_register(register)
# We already did the broadcasting and checking when the first call to
# QuantumCircuit.append happened (which the user wrote), and added the instruction into
# this scope. We just need to finish the job now.
Expand Down
12 changes: 6 additions & 6 deletions qiskit/circuit/controlflow/if_else.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from __future__ import annotations

from typing import Optional, Tuple, Union, Iterable
from typing import Optional, Union, Iterable
import itertools

from qiskit.circuit import ClassicalRegister, Clbit, QuantumCircuit
Expand Down Expand Up @@ -182,9 +182,9 @@ class IfElsePlaceholder(InstructionPlaceholder):

def __init__(
self,
condition: Tuple[Union[ClassicalRegister, Clbit], int],
condition: tuple[ClassicalRegister, int] | tuple[Clbit, int] | expr.Expr,
true_block: ControlFlowBuilderBlock,
false_block: Optional[ControlFlowBuilderBlock] = None,
false_block: ControlFlowBuilderBlock | None = None,
*,
label: Optional[str] = None,
):
Expand Down Expand Up @@ -333,10 +333,10 @@ class IfContext:
def __init__(
self,
circuit: QuantumCircuit,
condition: Tuple[Union[ClassicalRegister, Clbit], int],
condition: tuple[ClassicalRegister, int] | tuple[Clbit, int] | expr.Expr,
*,
in_loop: bool,
label: Optional[str] = None,
label: str | None = None,
):
self._circuit = circuit
self._condition = validate_condition(condition)
Expand All @@ -354,7 +354,7 @@ def circuit(self) -> QuantumCircuit:
return self._circuit

@property
def condition(self) -> Tuple[Union[ClassicalRegister, Clbit], int]:
def condition(self) -> tuple[ClassicalRegister, int] | tuple[Clbit, int] | expr.Expr:
"""Get the expression that this statement is conditioned on."""
return self._condition

Expand Down
36 changes: 23 additions & 13 deletions qiskit/circuit/controlflow/switch_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from .builder import InstructionPlaceholder, InstructionResources, ControlFlowBuilderBlock
from .control_flow import ControlFlowOp
from ._builder_utils import unify_circuit_resources, partition_registers
from ._builder_utils import unify_circuit_resources, partition_registers, node_resources


class _DefaultCaseType:
Expand Down Expand Up @@ -207,7 +207,7 @@ class SwitchCasePlaceholder(InstructionPlaceholder):

def __init__(
self,
target: Union[Clbit, ClassicalRegister],
target: Clbit | ClassicalRegister | expr.Expr,
cases: List[Tuple[Any, ControlFlowBuilderBlock]],
*,
label: Optional[str] = None,
Expand All @@ -230,9 +230,13 @@ def _calculate_placeholder_resources(self):
cregs = set()
if isinstance(self.__target, Clbit):
clbits.add(self.__target)
else:
elif isinstance(self.__target, ClassicalRegister):
clbits.update(self.__target)
cregs.add(self.__target)
else:
resources = node_resources(self.__target)
clbits.update(resources.clbits)
cregs.update(resources.cregs)
for _, body in self.__cases:
qubits |= body.qubits
clbits |= body.clbits
Expand Down Expand Up @@ -294,13 +298,23 @@ class SwitchContext:
def __init__(
self,
circuit: QuantumCircuit,
target: Union[Clbit, ClassicalRegister],
target: Clbit | ClassicalRegister | expr.Expr,
*,
in_loop: bool,
label: Optional[str] = None,
):
self.circuit = circuit
self.target = target
self._target = target
if isinstance(target, Clbit):
self.target_clbits: tuple[Clbit, ...] = (target,)
self.target_cregs: tuple[ClassicalRegister, ...] = ()
elif isinstance(target, ClassicalRegister):
self.target_clbits = tuple(target)
self.target_cregs = (target,)
else:
resources = node_resources(target)
self.target_clbits = resources.clbits
self.target_cregs = resources.cregs
self.in_loop = in_loop
self.complete = False
self._op_label = label
Expand Down Expand Up @@ -335,7 +349,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# If we're in a loop-builder context, we need to emit a placeholder so that any `break` or
# `continue`s in any of our cases can be expanded when the loop-builder. If we're not, we
# need to emit a concrete instruction immediately.
placeholder = SwitchCasePlaceholder(self.target, self._cases, label=self._op_label)
placeholder = SwitchCasePlaceholder(self._target, self._cases, label=self._op_label)
initial_resources = placeholder.placeholder_resources()
if self.in_loop:
self.circuit.append(placeholder, initial_resources.qubits, initial_resources.clbits)
Expand Down Expand Up @@ -386,14 +400,10 @@ def __call__(self, *values):
if self.switch.label_in_use(value) or value in seen:
raise CircuitError(f"duplicate case label: '{value}'")
seen.add(value)
if isinstance(self.switch.target, Clbit):
target_clbits = [self.switch.target]
target_registers = []
else:
target_clbits = list(self.switch.target)
target_registers = [self.switch.target]
self.switch.circuit._push_scope(
clbits=target_clbits, registers=target_registers, allow_jumps=self.switch.in_loop
clbits=self.switch.target_clbits,
registers=self.switch.target_cregs,
allow_jumps=self.switch.in_loop,
)

try:
Expand Down
10 changes: 2 additions & 8 deletions qiskit/circuit/controlflow/while_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

from __future__ import annotations

from typing import Union, Tuple, Optional

from qiskit.circuit import Clbit, ClassicalRegister, QuantumCircuit
from qiskit.circuit.classical import expr
from qiskit.circuit.exceptions import CircuitError
Expand Down Expand Up @@ -140,13 +138,9 @@ class WhileLoopContext:
def __init__(
self,
circuit: QuantumCircuit,
condition: Union[
Tuple[ClassicalRegister, int],
Tuple[Clbit, int],
Tuple[Clbit, bool],
],
condition: tuple[ClassicalRegister, int] | tuple[Clbit, int] | expr.Expr,
*,
label: Optional[str] = None,
label: str | None = None,
):

self._circuit = circuit
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
The control-flow builder interface will now correctly include :class:`.ClassicalRegister`
resources from nested switch statements in their containing circuit scopes. See `#10398
<https://github.com/Qiskit/qiskit-terra/issues/10398>`__.
Loading

0 comments on commit cf2170b

Please sign in to comment.