Skip to content

Commit

Permalink
Deprecate the condition attribute and related functionality (Qiskit#1…
Browse files Browse the repository at this point in the history
…3223)

* Deprecate the condition attribute and related functionality

This commit deprecates the Instruction.condition and c_if() method for
removal in 2.0. This functionality has been superseded by the more
sophisiticated `IfElseOp` for some time now. Removing the condition
property will simplify the Qiskit data model as it is implemented in
a kind of ad-hoc manner that adds additional overhead to manually check
it in many places.

For the unittest modifications the deprecation warning for the .condtion
getter is suppressed for the entire suite because this gets triggered
internally by the transpiler and a lot of other places, including from
rust code as until it is removed we need to use it to check that piece
of the data model.

Fixes Qiskit#9556

* Add missing assertWarns

* Handle deprecation warnings in visual tests too

* Apply suggestions from code review

Co-authored-by: Elena Peña Tapia <[email protected]>

* Use private attribute in py->rust conversion

* Avoid deprecation warning in non-deprecated code

This commit fixes some places in the code where we were using the
deprecated functionality where it needed a path to persist the behavior
or where the deprecation warning became a performance bottleneck. The
code is updated accordingly and to catch issues like this in the future
the warning filters are adjusted to be more selective.

* Add missing test updates

* Add filter for aer's condition usage and dag drawer

* Fix lint

---------

Co-authored-by: Elena Peña Tapia <[email protected]>
  • Loading branch information
mtreinish and ElePT authored Oct 31, 2024
1 parent 4e6fd36 commit c6efb76
Show file tree
Hide file tree
Showing 111 changed files with 2,231 additions and 1,127 deletions.
2 changes: 1 addition & 1 deletion crates/circuit/src/circuit_instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython {
ob.getattr(intern!(py, "label"))?.extract()?,
duration,
unit,
ob.getattr(intern!(py, "condition"))?.extract()?,
ob.getattr(intern!(py, "_condition"))?.extract()?,
))
};

Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/barrier.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from __future__ import annotations

from qiskit.exceptions import QiskitError
from qiskit.utils import deprecate_func
from .instruction import Instruction


Expand All @@ -44,5 +45,6 @@ def inverse(self, annotated: bool = False):
"""Special case. Return self."""
return Barrier(self.num_qubits)

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical, val):
raise QiskitError("Barriers are compiler directives and cannot be conditional.")
6 changes: 3 additions & 3 deletions qiskit/circuit/controlflow/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def _copy_mutable_properties(self, instruction: Instruction) -> Instruction:
The same instruction instance that was passed, but mutated to propagate the tracked
changes to this class.
"""
instruction.condition = 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 @@ -639,8 +639,8 @@ def update_registers(index, op):
# a register is already present, so we use our own tracking.
self.add_register(register)
out.add_register(register)
if getattr(op, "condition", None) is not None:
for register in condition_resources(op.condition).cregs:
if getattr(op, "_condition", None) is not None:
for register in condition_resources(op._condition).cregs:
if register not in self.registers:
self.add_register(register)
out.add_register(register)
Expand Down
18 changes: 13 additions & 5 deletions qiskit/circuit/controlflow/if_else.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,20 @@ def __init__(

super().__init__("if_else", num_qubits, num_clbits, [true_body, false_body], label=label)

self.condition = validate_condition(condition)
self._condition = validate_condition(condition)

@property
def params(self):
return self._params

@property
def condition(self):
return self._condition

@condition.setter
def condition(self, value):
self._condition = value

@params.setter
def params(self, parameters):
# pylint: disable=cyclic-import
Expand Down Expand Up @@ -152,7 +160,7 @@ def replace_blocks(self, blocks: Iterable[QuantumCircuit]) -> "IfElseOp":
true_body, false_body = (
ablock for ablock, _ in itertools.zip_longest(blocks, range(2), fillvalue=None)
)
return IfElseOp(self.condition, true_body, false_body=false_body, label=self.label)
return IfElseOp(self._condition, true_body, false_body=false_body, label=self.label)

def c_if(self, classical, val):
raise NotImplementedError(
Expand Down Expand Up @@ -200,7 +208,7 @@ def __init__(
"if_else", len(self.__resources.qubits), len(self.__resources.clbits), [], label=label
)
# Set the condition after super().__init__() has initialized it to None.
self.condition = validate_condition(condition)
self._condition = validate_condition(condition)

def with_false_block(self, false_block: ControlFlowBuilderBlock) -> "IfElsePlaceholder":
"""Return a new placeholder instruction, with the false block set to the given value,
Expand All @@ -225,7 +233,7 @@ def with_false_block(self, false_block: ControlFlowBuilderBlock) -> "IfElsePlace
false_bits = false_block.qubits() | false_block.clbits()
true_block.add_bits(false_bits - true_bits)
false_block.add_bits(true_bits - false_bits)
return type(self)(self.condition, true_block, false_block, label=self.label)
return type(self)(self._condition, true_block, false_block, label=self.label)

def registers(self):
"""Get the registers used by the interior blocks."""
Expand Down Expand Up @@ -288,7 +296,7 @@ def concrete_instruction(self, qubits, clbits):
)
return (
self._copy_mutable_properties(
IfElseOp(self.condition, true_body, false_body, label=self.label)
IfElseOp(self._condition, true_body, false_body, label=self.label)
),
InstructionResources(
qubits=tuple(true_body.qubits),
Expand Down
12 changes: 10 additions & 2 deletions qiskit/circuit/controlflow/while_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,20 @@ def __init__(
num_clbits = body.num_clbits

super().__init__("while_loop", num_qubits, num_clbits, [body], label=label)
self.condition = validate_condition(condition)
self._condition = validate_condition(condition)

@property
def params(self):
return self._params

@property
def condition(self):
return self._condition

@condition.setter
def condition(self, value):
self._condition = value

@params.setter
def params(self, parameters):
# pylint: disable=cyclic-import
Expand Down Expand Up @@ -88,7 +96,7 @@ def blocks(self):

def replace_blocks(self, blocks):
(body,) = blocks
return WhileLoopOp(self.condition, body, label=self.label)
return WhileLoopOp(self._condition, body, label=self.label)

def c_if(self, classical, val):
raise NotImplementedError(
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from qiskit.circuit.gate import Gate
from qiskit.circuit import _utils
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.utils import deprecate_func


@_utils.with_gate_array(np.eye(2, dtype=complex))
Expand Down Expand Up @@ -46,6 +47,7 @@ def inverse(self, annotated: bool = False):
"""Special case. Return self."""
return self

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical, val):
raise CircuitError("Conditional Delay is not yet implemented.")

Expand Down
17 changes: 10 additions & 7 deletions qiskit/circuit/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ def to_mutable(self):
return self.copy()

@property
@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0", is_property=True)
def condition(self):
"""The classical condition on the instruction."""
return self._condition
Expand Down Expand Up @@ -410,8 +411,8 @@ def _assemble(self):
# Add condition parameters for assembler. This is needed to convert
# to a qobj conditional instruction at assemble time and after
# conversion will be deleted by the assembler.
if self.condition:
instruction._condition = self.condition
if self._condition:
instruction._condition = self._condition
return instruction

@property
Expand Down Expand Up @@ -517,6 +518,7 @@ def inverse(self, annotated: bool = False):
inverse_gate.definition = inverse_definition
return inverse_gate

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical, val):
"""Set a classical equality condition on this instruction between the register or cbit
``classical`` and value ``val``.
Expand Down Expand Up @@ -632,26 +634,27 @@ def repeat(self, n):
qargs = tuple(qc.qubits)
cargs = tuple(qc.clbits)
base = self.copy()
if self.condition:
if self._condition:
# Condition is handled on the outer instruction.
base = base.to_mutable()
base.condition = None
for _ in [None] * n:
qc._append(CircuitInstruction(base, qargs, cargs))

instruction.definition = qc
if self.condition:
instruction = instruction.c_if(*self.condition)
if self._condition:
instruction = instruction.c_if(*self._condition)
return instruction

@property
@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0", is_property=True)
def condition_bits(self) -> List[Clbit]:
"""Get Clbits in condition."""
from qiskit.circuit.controlflow import condition_resources # pylint: disable=cyclic-import

if self.condition is None:
if self._condition is None:
return []
return list(condition_resources(self.condition).clbits)
return list(condition_resources(self._condition).clbits)

@property
def name(self):
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/instructionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import Callable

from qiskit.circuit.exceptions import CircuitError
from qiskit.utils import deprecate_func
from .classicalregister import Clbit, ClassicalRegister
from .operation import Operation
from .quantumcircuitdata import CircuitInstruction
Expand Down Expand Up @@ -105,6 +106,7 @@ def inverse(self, annotated: bool = False):
)
return self

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical: Clbit | ClassicalRegister | int, val: int) -> "InstructionSet":
"""Set a classical equality condition on all the instructions in this set between the
:obj:`.ClassicalRegister` or :obj:`.Clbit` ``classical`` and value ``val``.
Expand Down
8 changes: 4 additions & 4 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2102,14 +2102,14 @@ def map_vars(op):
is_control_flow = isinstance(n_op, ControlFlowOp)
if (
not is_control_flow
and (condition := getattr(n_op, "condition", None)) is not None
and (condition := getattr(n_op, "_condition", None)) is not None
):
n_op = n_op.copy() if n_op is op and copy else n_op
n_op.condition = variable_mapper.map_condition(condition)
elif is_control_flow:
n_op = n_op.replace_blocks(recurse_block(block) for block in n_op.blocks)
if isinstance(n_op, (IfElseOp, WhileLoopOp)):
n_op.condition = variable_mapper.map_condition(n_op.condition)
n_op.condition = variable_mapper.map_condition(n_op._condition)
elif isinstance(n_op, SwitchCaseOp):
n_op.target = variable_mapper.map_target(n_op.target)
elif isinstance(n_op, Store):
Expand Down Expand Up @@ -3520,7 +3520,7 @@ def update_from_expr(objects, node):

for instruction in self._data:
objects = set(itertools.chain(instruction.qubits, instruction.clbits))
if (condition := getattr(instruction.operation, "condition", None)) is not None:
if (condition := getattr(instruction.operation, "_condition", None)) is not None:
objects.update(_builder_utils.condition_resources(condition).clbits)
if isinstance(condition, expr.Expr):
update_from_expr(objects, condition)
Expand Down Expand Up @@ -3623,7 +3623,7 @@ def num_connected_components(self, unitary_only: bool = False) -> int:
else:
args = instruction.qubits + instruction.clbits
num_qargs = len(args) + (
1 if getattr(instruction.operation, "condition", None) else 0
1 if getattr(instruction.operation, "_condition", None) else 0
)

if num_qargs >= 2 and not getattr(instruction.operation, "_directive", False):
Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/singleton.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ class XGate(Gate, metaclass=_SingletonMeta, overrides=_SingletonGateOverrides):

import functools

from qiskit.utils import deprecate_func
from .instruction import Instruction
from .gate import Gate
from .controlledgate import ControlledGate, _ctrl_state_to_int
Expand Down Expand Up @@ -489,6 +490,7 @@ class they are providing overrides for has more lazy attributes or user-exposed
instruction._params = _frozenlist(instruction._params)
return instruction

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical, val):
return self.to_mutable().c_if(classical, val)

Expand Down
2 changes: 2 additions & 0 deletions qiskit/circuit/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import typing

from qiskit.utils import deprecate_func
from .exceptions import CircuitError
from .classical import expr, types
from .instruction import Instruction
Expand Down Expand Up @@ -88,6 +89,7 @@ def rvalue(self):
"""Get the r-value :class:`~.expr.Expr` node that is being written into the l-value."""
return self.params[1]

@deprecate_func(since="1.3.0", removal_timeline="in 2.0.0")
def c_if(self, classical, val):
""":meta hidden:"""
raise NotImplementedError(
Expand Down
2 changes: 1 addition & 1 deletion qiskit/converters/circuit_to_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def fix_condition(op):
if (out := operation_map.get(original_id)) is not None:
return out

condition = getattr(op, "condition", None)
condition = getattr(op, "_condition", None)
if condition:
reg, val = condition
if isinstance(reg, Clbit):
Expand Down
6 changes: 3 additions & 3 deletions qiskit/dagcircuit/collect_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def split_block_into_layers(block: list[DAGOpNode | DAGDepNode]):
cur_bits = set(node.qargs)
cur_bits.update(node.cargs)

cond = getattr(node.op, "condition", None)
cond = getattr(node.op, "_condition", None)
if cond is not None:
cur_bits.update(condition_resources(cond).clbits)

Expand Down Expand Up @@ -356,7 +356,7 @@ def collapse_to_operation(self, blocks, collapse_fn):
for node in block:
cur_qubits.update(node.qargs)
cur_clbits.update(node.cargs)
cond = getattr(node.op, "condition", None)
cond = getattr(node.op, "_condition", None)
if cond is not None:
cur_clbits.update(condition_resources(cond).clbits)
if isinstance(cond[0], ClassicalRegister):
Expand All @@ -378,7 +378,7 @@ def collapse_to_operation(self, blocks, collapse_fn):

for node in block:
instructions = qc.append(CircuitInstruction(node.op, node.qargs, node.cargs))
cond = getattr(node.op, "condition", None)
cond = getattr(node.op, "_condition", None)
if cond is not None:
instructions.c_if(*cond)

Expand Down
6 changes: 3 additions & 3 deletions qiskit/dagcircuit/dagdependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,13 +409,13 @@ def _create_op_node(self, operation, qargs, cargs):
for elem in qargs:
qindices_list.append(self.qubits.index(elem))

if getattr(operation, "condition", None):
if getattr(operation, "_condition", None):
# The change to handling operation.condition follows code patterns in quantum_circuit.py.
# However:
# (1) cindices_list are specific to template optimization and should not be computed
# in this place.
# (2) Template optimization pass needs currently does not handle general conditions.
cond_bits = condition_resources(operation.condition).clbits
cond_bits = condition_resources(operation._condition).clbits
cindices_list = [self.clbits.index(clbit) for clbit in cond_bits]
else:
cindices_list = []
Expand Down Expand Up @@ -609,7 +609,7 @@ def replace_block_with_op(self, node_block, op, wire_pos_map, cycle_check=True):
for nd in node_block:
block_qargs |= set(nd.qargs)
block_cargs |= set(nd.cargs)
cond = getattr(nd.op, "condition", None)
cond = getattr(nd.op, "_condition", None)
if cond is not None:
block_cargs.update(condition_resources(cond).clbits)

Expand Down
4 changes: 2 additions & 2 deletions qiskit/dagcircuit/dagnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ def key(var):


def _condition_op_eq(node1, node2, bit_indices1, bit_indices2):
cond1 = node1.op.condition
cond2 = node2.op.condition
cond1 = node1.condition
cond2 = node2.condition
if isinstance(cond1, expr.Expr) and isinstance(cond2, expr.Expr):
if not expr.structurally_equivalent(
cond1, cond2, _make_expr_key(bit_indices1), _make_expr_key(bit_indices2)
Expand Down
2 changes: 1 addition & 1 deletion qiskit/primitives/statevector_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,6 @@ def _final_measurement_mapping(circuit: QuantumCircuit) -> dict[tuple[ClassicalR

def _has_control_flow(circuit: QuantumCircuit) -> bool:
return any(
isinstance((op := instruction.operation), ControlFlowOp) or op.condition
isinstance((op := instruction.operation), ControlFlowOp) or op._condition
for instruction in circuit
)
8 changes: 5 additions & 3 deletions qiskit/qasm2/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,14 @@ def _instruction_call_site(operation):
if operation.params:
params = ",".join([pi_check(i, output="qasm", eps=1e-12) for i in operation.params])
qasm2_call = f"{qasm2_call}({params})"
if operation.condition is not None:
if not isinstance(operation.condition[0], ClassicalRegister):
if operation._condition is not None:
if not isinstance(operation._condition[0], ClassicalRegister):
raise QASM2ExportError(
"OpenQASM 2 can only condition on registers, but got '{operation.condition[0]}'"
)
qasm2_call = f"if({operation.condition[0].name}=={operation.condition[1]:d}) " + qasm2_call
qasm2_call = (
f"if({operation._condition[0].name}=={operation._condition[1]:d}) " + qasm2_call
)
return qasm2_call


Expand Down
Loading

0 comments on commit c6efb76

Please sign in to comment.