diff --git a/qiskit/circuit/__init__.py b/qiskit/circuit/__init__.py index 62c6c524eb29..a8d57f615d7c 100644 --- a/qiskit/circuit/__init__.py +++ b/qiskit/circuit/__init__.py @@ -280,7 +280,6 @@ InstructionSet Operation EquivalenceLibrary - Store Control Flow Operations ----------------------- @@ -377,7 +376,6 @@ from .delay import Delay from .measure import Measure from .reset import Reset -from .store import Store from .parameter import Parameter from .parametervector import ParameterVector from .parameterexpression import ParameterExpression diff --git a/qiskit/circuit/classical/expr/__init__.py b/qiskit/circuit/classical/expr/__init__.py index 4502aa52779a..c256040e5b80 100644 --- a/qiskit/circuit/classical/expr/__init__.py +++ b/qiskit/circuit/classical/expr/__init__.py @@ -39,11 +39,10 @@ These objects are mutable and should not be reused in a different location without a copy. -The base for dynamic variables is the :class:`Var`, which can be either an arbitrarily typed runtime -variable, or a wrapper around a :class:`.Clbit` or :class:`.ClassicalRegister`. +The entry point from general circuit objects to the expression system is by wrapping the object +in a :class:`Var` node and associating a :class:`~.types.Type` with it. .. autoclass:: Var - :members: var, name Similarly, literals used in comparison (such as integers) should be lifted to :class:`Value` nodes with associated types. @@ -92,13 +91,6 @@ .. autofunction:: lift -Typically you should create memory-owning :class:`Var` instances by using the -:meth:`.QuantumCircuit.add_var` method to declare them in some circuit context, since a -:class:`.QuantumCircuit` will not accept an :class:`Expr` that contains variables that are not -already declared in it, since it needs to know how to allocate the storage and how the variable will -be initialized. However, should you want to do this manually, you should use the low-level -:meth:`Var.new` call to safely generate a named variable for usage. - You can manually specify casts in cases where the cast is allowed in explicit form, but may be lossy (such as the cast of a higher precision :class:`~.types.Uint` to a lower precision one). @@ -160,11 +152,6 @@ suitable "key" functions to do the comparison. .. autofunction:: structurally_equivalent - -Some expressions have associated memory locations, and others may be purely temporary. -You can use :func:`is_lvalue` to determine whether an expression has an associated memory location. - -.. autofunction:: is_lvalue """ __all__ = [ @@ -177,7 +164,6 @@ "ExprVisitor", "iter_vars", "structurally_equivalent", - "is_lvalue", "lift", "cast", "bit_not", @@ -197,7 +183,7 @@ ] from .expr import Expr, Var, Value, Cast, Unary, Binary -from .visitors import ExprVisitor, iter_vars, structurally_equivalent, is_lvalue +from .visitors import ExprVisitor, iter_vars, structurally_equivalent from .constructors import ( lift, cast, diff --git a/qiskit/circuit/classical/expr/expr.py b/qiskit/circuit/classical/expr/expr.py index c22870e51fee..399a919ca071 100644 --- a/qiskit/circuit/classical/expr/expr.py +++ b/qiskit/circuit/classical/expr/expr.py @@ -31,7 +31,6 @@ import abc import enum import typing -import uuid from .. import types @@ -111,49 +110,29 @@ def __repr__(self): class Var(Expr): """A classical variable. - These variables take two forms: a new-style variable that owns its storage location and has an - associated name; and an old-style variable that wraps a :class:`.Clbit` or - :class:`.ClassicalRegister` instance that is owned by some containing circuit. In general, - construction of variables for use in programs should use :meth:`Var.new` or - :meth:`.QuantumCircuit.add_var`. - Variables are immutable after construction, so they can be used as dictionary keys.""" - __slots__ = ("var", "name") + __slots__ = ("var",) - var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID + var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister """A reference to the backing data storage of the :class:`Var` instance. When lifting old-style :class:`.Clbit` or :class:`.ClassicalRegister` instances into a :class:`Var`, - this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`. If the variable is a - new-style classical variable (one that owns its own storage separate to the old - :class:`.Clbit`/:class:`.ClassicalRegister` model), this field will be a :class:`~uuid.UUID` - to uniquely identify it.""" - name: str | None - """The name of the variable. This is required to exist if the backing :attr:`var` attribute - is a :class:`~uuid.UUID`, i.e. if it is a new-style variable, and must be ``None`` if it is - an old-style variable.""" + this is exactly the :class:`.Clbit` or :class:`.ClassicalRegister`.""" def __init__( self, - var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID, + var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister, type: types.Type, - *, - name: str | None = None, ): super().__setattr__("type", type) super().__setattr__("var", var) - super().__setattr__("name", name) - - @classmethod - def new(cls, name: str, type: types.Type) -> typing.Self: - """Generate a new named variable that owns its own backing storage.""" - return cls(uuid.uuid4(), type, name=name) @property def standalone(self) -> bool: - """Whether this :class:`Var` is a standalone variable that owns its storage location. If - false, this is a wrapper :class:`Var` around a pre-existing circuit object.""" - return isinstance(self.var, uuid.UUID) + """Whether this :class:`Var` is a standalone variable that owns its storage location. + This is currently always ``False``, but will expand in the future to support memory-owning + storage locations.""" + return False def accept(self, visitor, /): return visitor.visit_var(self) @@ -164,29 +143,21 @@ def __setattr__(self, key, value): raise AttributeError(f"'Var' object has no attribute '{key}'") def __hash__(self): - return hash((self.type, self.var, self.name)) + return hash((self.type, self.var)) def __eq__(self, other): - return ( - isinstance(other, Var) - and self.type == other.type - and self.var == other.var - and self.name == other.name - ) + return isinstance(other, Var) and self.type == other.type and self.var == other.var def __repr__(self): - if self.name is None: - return f"Var({self.var}, {self.type})" - return f"Var({self.var}, {self.type}, name='{self.name}')" + return f"Var({self.var}, {self.type})" def __getstate__(self): - return (self.var, self.type, self.name) + return (self.var, self.type) def __setstate__(self, state): - var, type, name = state + var, type = state super().__setattr__("type", type) super().__setattr__("var", var) - super().__setattr__("name", name) def __copy__(self): # I am immutable... diff --git a/qiskit/circuit/classical/expr/visitors.py b/qiskit/circuit/classical/expr/visitors.py index c0c1a5894af6..07ad36a8e0e4 100644 --- a/qiskit/circuit/classical/expr/visitors.py +++ b/qiskit/circuit/classical/expr/visitors.py @@ -215,66 +215,3 @@ def structurally_equivalent( True """ return left.accept(_StructuralEquivalenceImpl(right, left_var_key, right_var_key)) - - -class _IsLValueImpl(ExprVisitor[bool]): - __slots__ = () - - def visit_var(self, node, /): - return True - - def visit_value(self, node, /): - return False - - def visit_unary(self, node, /): - return False - - def visit_binary(self, node, /): - return False - - def visit_cast(self, node, /): - return False - - -_IS_LVALUE = _IsLValueImpl() - - -def is_lvalue(node: expr.Expr, /) -> bool: - """Return whether this expression can be used in l-value positions, that is, whether it has a - well-defined location in memory, such as one that might be writeable. - - Being an l-value is a necessary but not sufficient for this location to be writeable; it is - permissible that a larger object containing this memory location may not allow writing from - the scope that attempts to write to it. This would be an access property of the containing - program, however, and not an inherent property of the expression system. - - Examples: - Literal values are never l-values; there's no memory location associated with (for example) - the constant ``1``:: - - >>> from qiskit.circuit.classical import expr - >>> expr.is_lvalue(expr.lift(2)) - False - - :class:`~.expr.Var` nodes are always l-values, because they always have some associated - memory location:: - - >>> from qiskit.circuit.classical import types - >>> from qiskit.circuit import Clbit - >>> expr.is_lvalue(expr.Var.new("a", types.Bool())) - True - >>> expr.is_lvalue(expr.lift(Clbit())) - True - - Currently there are no unary or binary operations on variables that can produce an l-value - expression, but it is likely in the future that some sort of "indexing" operation will be - added, which could produce l-values:: - - >>> a = expr.Var.new("a", types.Uint(8)) - >>> b = expr.Var.new("b", types.Uint(8)) - >>> expr.is_lvalue(a) and expr.is_lvalue(b) - True - >>> expr.is_lvalue(expr.bit_and(a, b)) - False - """ - return node.accept(_IS_LVALUE) diff --git a/qiskit/circuit/controlflow/builder.py b/qiskit/circuit/controlflow/builder.py index 38530c0dd8f3..dd174ff4d1f5 100644 --- a/qiskit/circuit/controlflow/builder.py +++ b/qiskit/circuit/controlflow/builder.py @@ -25,7 +25,6 @@ from typing import Collection, Iterable, FrozenSet, Tuple, Union, Optional, Sequence from qiskit._accelerate.quantum_circuit import CircuitData -from qiskit.circuit.classical import expr from qiskit.circuit.classicalregister import Clbit, ClassicalRegister from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.instruction import Instruction @@ -104,66 +103,6 @@ def resolve_classical_resource( or a :class:`.Clbit` that isn't actually in the circuit. """ - @abc.abstractmethod - def add_uninitialized_var(self, var: expr.Var): - """Add an uninitialized variable to the circuit scope. - - The general circuit context is responsible for ensuring the variable is initialized. These - uninitialized variables are guaranteed to be standalone. - - Args: - var: the variable to add, if valid. - - Raises: - CircuitError: if the variable cannot be added, such as because it invalidly shadows or - redefines an existing name. - """ - - @abc.abstractmethod - def remove_var(self, var: expr.Var): - """Remove a variable from the locals of this scope. - - This is only called in the case that an exception occurred while initializing the variable, - and is not exposed to users. - - Args: - var: the variable to remove. It can be assumed that this was already the subject of an - :meth:`add_uninitialized_var` call. - """ - - @abc.abstractmethod - def use_var(self, var: expr.Var): - """Called for every standalone classical runtime variable being used by some circuit - instruction. - - The given variable is guaranteed to be a stand-alone variable; bit-like resource-wrapping - variables will have been filtered out and their resources given to - :meth:`resolve_classical_resource`. - - Args: - var: the variable to validate. - - Returns: - the same variable. - - Raises: - CircuitError: if the variable is not valid for this scope. - """ - - @abc.abstractmethod - def get_var(self, name: str) -> Optional[expr.Var]: - """Get the variable (if any) in scope with the given name. - - This should call up to the parent scope if in a control-flow builder scope, in case the - variable exists in an outer scope. - - Args: - name: the name of the symbol to lookup. - - Returns: - the variable if it is found, otherwise ``None``. - """ - class InstructionResources(typing.NamedTuple): """The quantum and classical resources used within a particular instruction. @@ -332,8 +271,6 @@ class ControlFlowBuilderBlock(CircuitScopeInterface): "_parent", "_built", "_forbidden_message", - "_vars_local", - "_vars_capture", ) def __init__( @@ -374,8 +311,6 @@ def __init__( self._instructions = CircuitData(qubits, clbits) self.registers = set(registers) self.global_phase = 0.0 - self._vars_local = {} - self._vars_capture = {} self._allow_jumps = allow_jumps self._parent = parent self._built = False @@ -458,49 +393,6 @@ def resolve_classical_resource(self, specifier): self.add_register(resource) return resource - def add_uninitialized_var(self, var: expr.Var): - if self._built: - raise CircuitError("Cannot add resources after the scope has been built.") - # We can shadow a name if it was declared in an outer scope, but only if we haven't already - # captured it ourselves yet. - if (previous := self._vars_local.get(var.name)) is not None: - if previous == var: - raise CircuitError(f"'{var}' is already present in the scope") - raise CircuitError(f"cannot add '{var}' as its name shadows the existing '{previous}'") - if var.name in self._vars_capture: - raise CircuitError(f"cannot add '{var}' as its name shadows the existing '{previous}'") - self._vars_local[var.name] = var - - def remove_var(self, var: expr.Var): - if self._built: - raise RuntimeError("exception handler 'remove_var' called after scope built") - self._vars_local.pop(var.name) - - def get_var(self, name: str): - if (out := self._vars_local.get(name)) is not None: - return out - return self._parent.get_var(name) - - def use_var(self, var: expr.Var): - if (local := self._vars_local.get(var.name)) is not None: - if local == var: - return - raise CircuitError(f"cannot use '{var}' which is shadowed by the local '{local}'") - if self._vars_capture.get(var.name) == var: - return - if self._parent.get_var(var.name) != var: - raise CircuitError(f"cannot close over '{var}', which is not in scope") - self._parent.use_var(var) - self._vars_capture[var.name] = var - - def iter_local_vars(self): - """Iterator over the variables currently declared in this scope.""" - return self._vars_local.values() - - def iter_captured_vars(self): - """Iterator over the variables currently captured in this scope.""" - return self._vars_capture.values() - def peek(self) -> CircuitInstruction: """Get the value of the most recent instruction tuple in this scope.""" if not self._instructions: @@ -601,12 +493,7 @@ def build( self._instructions.clbits, *self.registers, global_phase=self.global_phase, - captures=self._vars_capture.values(), ) - for var in self._vars_local.values(): - # The requisite `Store` instruction to initialise the variable will have been appended - # into the instructions. - out.add_uninitialized_var(var) # Maps placeholder index to the newly concrete instruction. placeholder_to_concrete = {} @@ -679,8 +566,6 @@ def copy(self) -> "ControlFlowBuilderBlock": out._instructions = self._instructions.copy() out.registers = self.registers.copy() out.global_phase = self.global_phase - out._vars_local = self._vars_local.copy() - out._vars_capture = self._vars_capture.copy() out._parent = self._parent out._allow_jumps = self._allow_jumps out._forbidden_message = self._forbidden_message diff --git a/qiskit/circuit/controlflow/control_flow.py b/qiskit/circuit/controlflow/control_flow.py index fefa27efa27f..4a4e09d88b99 100644 --- a/qiskit/circuit/controlflow/control_flow.py +++ b/qiskit/circuit/controlflow/control_flow.py @@ -18,7 +18,6 @@ from abc import ABC, abstractmethod from qiskit.circuit.instruction import Instruction -from qiskit.circuit.exceptions import CircuitError if typing.TYPE_CHECKING: from qiskit.circuit import QuantumCircuit @@ -27,12 +26,6 @@ class ControlFlowOp(Instruction, ABC): """Abstract class to encapsulate all control flow operations.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - for block in self.blocks: - if block.num_input_vars: - raise CircuitError("control-flow blocks cannot contain input variables") - @property @abstractmethod def blocks(self) -> tuple[QuantumCircuit, ...]: diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 4c179a2fac68..3e994ca93cee 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -16,7 +16,6 @@ from __future__ import annotations import copy -import itertools import multiprocessing as mp import warnings import typing @@ -46,7 +45,6 @@ from qiskit.circuit.exceptions import CircuitError from . import _classical_resource_map from ._utils import sort_parameters -from .controlflow import ControlFlowOp from .controlflow.builder import CircuitScopeInterface, ControlFlowBuilderBlock from .controlflow.break_loop import BreakLoopOp, BreakLoopPlaceholder from .controlflow.continue_loop import ContinueLoopOp, ContinueLoopPlaceholder @@ -54,7 +52,7 @@ from .controlflow.if_else import IfElseOp, IfContext from .controlflow.switch_case import SwitchCaseOp, SwitchContext from .controlflow.while_loop import WhileLoopOp, WhileLoopContext -from .classical import expr, types +from .classical import expr from .parameterexpression import ParameterExpression, ParameterValueType from .quantumregister import QuantumRegister, Qubit, AncillaRegister, AncillaQubit from .classicalregister import ClassicalRegister, Clbit @@ -66,7 +64,6 @@ from .bit import Bit from .quantumcircuitdata import QuantumCircuitData, CircuitInstruction from .delay import Delay -from .store import Store if typing.TYPE_CHECKING: import qiskit # pylint: disable=cyclic-import @@ -145,27 +142,9 @@ class QuantumCircuit: circuit. This gets stored as free-form data in a dict in the :attr:`~qiskit.circuit.QuantumCircuit.metadata` attribute. It will not be directly used in the circuit. - inputs: any variables to declare as ``input`` runtime variables for this circuit. These - should already be existing :class:`.expr.Var` nodes that you build from somewhere else; - if you need to create the inputs as well, use :meth:`QuantumCircuit.add_input`. The - variables given in this argument will be passed directly to :meth:`add_input`. A - circuit cannot have both ``inputs`` and ``captures``. - captures: any variables that that this circuit scope should capture from a containing scope. - The variables given here will be passed directly to :meth:`add_capture`. A circuit - cannot have both ``inputs`` and ``captures``. - declarations: any variables that this circuit should declare and initialize immediately. - You can order this input so that later declarations depend on earlier ones (including - inputs or captures). If you need to depend on values that will be computed later at - runtime, use :meth:`add_var` at an appropriate point in the circuit execution. - - This argument is intended for convenient circuit initialization when you already have a - set of created variables. The variables used here will be directly passed to - :meth:`add_var`, which you can use directly if this is the first time you are creating - the variable. Raises: CircuitError: if the circuit name, if given, is not valid. - CircuitError: if both ``inputs`` and ``captures`` are given. Examples: @@ -225,9 +204,6 @@ def __init__( name: str | None = None, global_phase: ParameterValueType = 0, metadata: dict | None = None, - inputs: Iterable[expr.Var] = (), - captures: Iterable[expr.Var] = (), - declarations: Mapping[expr.Var, expr.Expr] | Iterable[Tuple[expr.Var, expr.Expr]] = (), ): if any(not isinstance(reg, (list, QuantumRegister, ClassicalRegister)) for reg in regs): # check if inputs are integers, but also allow e.g. 2.0 @@ -301,20 +277,6 @@ def __init__( self._global_phase: ParameterValueType = 0 self.global_phase = global_phase - # Add classical variables. Resolve inputs and captures first because they can't depend on - # anything, but declarations might depend on them. - self._vars_input: dict[str, expr.Var] = {} - self._vars_capture: dict[str, expr.Var] = {} - self._vars_local: dict[str, expr.Var] = {} - for input_ in inputs: - self.add_input(input_) - for capture in captures: - self.add_capture(capture) - if isinstance(declarations, Mapping): - declarations = declarations.items() - for var, initial in declarations: - self.add_var(var, initial) - self.duration = None self.unit = "dt" self.metadata = {} if metadata is None else metadata @@ -1167,74 +1129,6 @@ def ancillas(self) -> list[AncillaQubit]: """ return self._ancillas - @property - def num_vars(self) -> int: - """The number of runtime classical variables in the circuit. - - This is the length of the :meth:`iter_vars` iterable.""" - return self.num_input_vars + self.num_captured_vars + self.num_declared_vars - - @property - def num_input_vars(self) -> int: - """The number of runtime classical variables in the circuit marked as circuit inputs. - - This is the length of the :meth:`iter_input_vars` iterable. If this is non-zero, - :attr:`num_captured_vars` must be zero.""" - return len(self._vars_input) - - @property - def num_captured_vars(self) -> int: - """The number of runtime classical variables in the circuit marked as captured from an - enclosing scope. - - This is the length of the :meth:`iter_captured_vars` iterable. If this is non-zero, - :attr:`num_input_vars` must be zero.""" - return len(self._vars_capture) - - @property - def num_declared_vars(self) -> int: - """The number of runtime classical variables in the circuit that are declared by this - circuit scope, excluding inputs or captures. - - This is the length of the :meth:`iter_declared_vars` iterable.""" - return len(self._vars_local) - - def iter_vars(self) -> typing.Iterable[expr.Var]: - """Get an iterable over all runtime classical variables in scope within this circuit. - - This method will iterate over all variables in scope. For more fine-grained iterators, see - :meth:`iter_declared_vars`, :meth:`iter_input_vars` and :meth:`iter_captured_vars`.""" - if self._control_flow_scopes: - builder = self._control_flow_scopes[-1] - return itertools.chain(builder.iter_captured_vars(), builder.iter_local_vars()) - return itertools.chain( - self._vars_input.values(), self._vars_capture.values(), self._vars_local.values() - ) - - def iter_declared_vars(self) -> typing.Iterable[expr.Var]: - """Get an iterable over all runtime classical variables that are declared with automatic - storage duration in this scope. This excludes input variables (see :meth:`iter_input_vars`) - and captured variables (see :meth:`iter_captured_vars`).""" - if self._control_flow_scopes: - return self._control_flow_scopes[-1].iter_local_vars() - return self._vars_local.values() - - def iter_input_vars(self) -> typing.Iterable[expr.Var]: - """Get an iterable over all runtime classical variables that are declared as inputs to this - circuit scope. This excludes locally declared variables (see :meth:`iter_declared_vars`) - and captured variables (see :meth:`iter_captured_vars`).""" - if self._control_flow_scopes: - return () - return self._vars_input.values() - - def iter_captured_vars(self) -> typing.Iterable[expr.Var]: - """Get an iterable over all runtime classical variables that are captured by this circuit - scope from a containing scope. This excludes input variables (see :meth:`iter_input_vars`) - and locally declared variables (see :meth:`iter_declared_vars`).""" - if self._control_flow_scopes: - return self._control_flow_scopes[-1].iter_captured_vars() - return self._vars_capture.values() - def __and__(self, rhs: "QuantumCircuit") -> "QuantumCircuit": """Overload & to implement self.compose.""" return self.compose(rhs) @@ -1372,20 +1266,6 @@ def append( param = _validate_expr(circuit_scope, param) if is_parameter: operation = copy.deepcopy(operation) - if isinstance(operation, ControlFlowOp): - # Verify that any variable bindings are valid. Control-flow ops are already enforced - # by the class not to contain 'input' variables. - if bad_captures := { - var - for var in itertools.chain.from_iterable( - block.iter_captured_vars() for block in operation.blocks - ) - if not self.has_var(var) - }: - raise CircuitError( - f"Control-flow op attempts to capture '{bad_captures}'" - " which are not in this circuit" - ) expanded_qargs = [self.qbit_argument_conversion(qarg) for qarg in qargs or []] expanded_cargs = [self.cbit_argument_conversion(carg) for carg in cargs or []] @@ -1536,11 +1416,6 @@ def get_parameter(self, name: str, default: typing.Any = ...) -> Parameter: assert qc.get_parameter("my_param", None) is my_param assert qc.get_parameter("unknown_param", None) is None - - See also: - :meth:`get_var` - A similar method, but for :class:`.expr.Var` run-time variables instead of - :class:`.Parameter` compile-time parameters. """ if (parameter := self._parameter_table.parameter_from_name(name, None)) is None: if default is Ellipsis: @@ -1562,307 +1437,11 @@ def has_parameter(self, name_or_param: str | Parameter, /) -> bool: See also: :meth:`QuantumCircuit.get_parameter` Retrieve the :class:`.Parameter` instance from this circuit by name. - :meth:`QuantumCircuit.has_var` - A similar method to this, but for run-time :class:`.expr.Var` variables instead of - compile-time :class:`.Parameter`\\ s. """ if isinstance(name_or_param, str): return self.get_parameter(name_or_param, None) is not None return self.get_parameter(name_or_param.name) == name_or_param - @typing.overload - def get_var(self, name: str, default: T) -> Union[expr.Var, T]: - ... - - # The builtin `types` module has `EllipsisType`, but only from 3.10+! - @typing.overload - def get_var(self, name: str, default: type(...) = ...) -> expr.Var: - ... - - # We use a _literal_ `Ellipsis` as the marker value to leave `None` available as a default. - def get_var(self, name: str, default: typing.Any = ...): - """Retrieve a variable that is accessible in this circuit scope by name. - - Args: - name: the name of the variable to retrieve. - default: if given, this value will be returned if the variable is not present. If it - is not given, a :exc:`KeyError` is raised instead. - - Returns: - The corresponding variable. - - Raises: - KeyError: if no default is given, but the variable does not exist. - - Examples: - Retrieve a variable by name from a circuit:: - - from qiskit.circuit import QuantumCircuit - - # Create a circuit and create a variable in it. - qc = QuantumCircuit() - my_var = qc.add_var("my_var", False) - - # We can use 'my_var' as a variable, but let's say we've lost the Python object and - # need to retrieve it. - my_var_again = qc.get_var("my_var") - - assert my_var is my_var_again - - Get a variable from a circuit by name, returning some default if it is not present:: - - assert qc.get_var("my_var", None) is my_var - assert qc.get_var("unknown_variable", None) is None - - See also: - :meth:`get_parameter` - A similar method, but for :class:`.Parameter` compile-time parameters instead of - :class:`.expr.Var` run-time variables. - """ - if (out := self._current_scope().get_var(name)) is not None: - return out - if default is Ellipsis: - raise KeyError(f"no variable named '{name}' is present") - return default - - def has_var(self, name_or_var: str | expr.Var, /) -> bool: - """Check whether a variable is accessible in this scope. - - Args: - name_or_var: the variable, or name of a variable to check. If this is a - :class:`.expr.Var` node, the variable must be exactly the given one for this - function to return ``True``. - - Returns: - whether a matching variable is accessible. - - See also: - :meth:`QuantumCircuit.get_var` - Retrieve the :class:`.expr.Var` instance from this circuit by name. - :meth:`QuantumCircuit.has_parameter` - A similar method to this, but for compile-time :class:`.Parameter`\\ s instead of - run-time :class:`.expr.Var` variables. - """ - if isinstance(name_or_var, str): - return self.get_var(name_or_var, None) is not None - return self.get_var(name_or_var.name, None) == name_or_var - - def _prepare_new_var( - self, name_or_var: str | expr.Var, type_: types.Type | None, / - ) -> expr.Var: - """The common logic for preparing and validating a new :class:`~.expr.Var` for the circuit. - - The given ``type_`` can be ``None`` if the variable specifier is already a :class:`.Var`, - and must be a :class:`~.types.Type` if it is a string. The argument is ignored if the given - first argument is a :class:`.Var` already. - - Returns the validated variable, which is guaranteed to be safe to add to the circuit.""" - if isinstance(name_or_var, str): - if type_ is None: - raise CircuitError("the type must be known when creating a 'Var' from a string") - var = expr.Var.new(name_or_var, type_) - else: - var = name_or_var - if not var.standalone: - raise CircuitError( - "cannot add variables that wrap `Clbit` or `ClassicalRegister` instances." - " Use `add_bits` or `add_register` as appropriate." - ) - - # The `var` is guaranteed to have a name because we already excluded the cases where it's - # wrapping a bit/register. - if (previous := self.get_var(var.name, default=None)) is not None: - if previous == var: - raise CircuitError(f"'{var}' is already present in the circuit") - raise CircuitError(f"cannot add '{var}' as its name shadows the existing '{previous}'") - return var - - def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.Var: - """Add a classical variable with automatic storage and scope to this circuit. - - The variable is considered to have been "declared" at the beginning of the circuit, but it - only becomes initialized at the point of the circuit that you call this method, so it can - depend on variables defined before it. - - Args: - name_or_var: either a string of the variable name, or an existing instance of - :class:`~.expr.Var` to re-use. Variables cannot shadow names that are already in - use within the circuit. - initial: the value to initialize this variable with. If the first argument was given - as a string name, the type of the resulting variable is inferred from the initial - expression; to control this more manually, either use :meth:`.Var.new` to manually - construct a new variable with the desired type, or use :func:`.expr.cast` to cast - the initializer to the desired type. - - This must be either a :class:`~.expr.Expr` node, or a value that can be lifted to - one using :class:`.expr.lift`. - - Returns: - The created variable. If a :class:`~.expr.Var` instance was given, the exact same - object will be returned. - - Raises: - CircuitError: if the variable cannot be created due to shadowing an existing variable. - - Examples: - Define a new variable given just a name and an initializer expression:: - - from qiskit.circuit import QuantumCircuit - - qc = QuantumCircuit(2) - my_var = qc.add_var("my_var", False) - - Reuse a variable that may have been taken from a related circuit, or otherwise - constructed manually, and initialize it to some more complicated expression:: - - from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister - from qiskit.circuit.classical import expr, types - - my_var = expr.Var.new("my_var", types.Uint(8)) - - cr1 = ClassicalRegister(8, "cr1") - cr2 = ClassicalRegister(8, "cr2") - qc = QuantumCircuit(QuantumRegister(8), cr1, cr2) - - # Get some measurement results into each register. - qc.h(0) - for i in range(1, 8): - qc.cx(0, i) - qc.measure(range(8), cr1) - - qc.reset(range(8)) - qc.h(0) - for i in range(1, 8): - qc.cx(0, i) - qc.measure(range(8), cr2) - - # Now when we add the variable, it is initialized using the runtime state of the two - # classical registers we measured into above. - qc.add_var(my_var, expr.bit_and(cr1, cr2)) - """ - # Validate the initialiser first to catch cases where the variable to be declared is being - # used in the initialiser. - circuit_scope = self._current_scope() - initial = _validate_expr(circuit_scope, expr.lift(initial)) - if isinstance(name_or_var, str): - var = expr.Var.new(name_or_var, initial.type) - elif not name_or_var.standalone: - raise CircuitError( - "cannot add variables that wrap `Clbit` or `ClassicalRegister` instances." - ) - else: - var = name_or_var - circuit_scope.add_uninitialized_var(var) - try: - # Store is responsible for ensuring the type safety of the initialisation. - store = Store(var, initial) - except CircuitError: - circuit_scope.remove_var(var) - raise - circuit_scope.append(CircuitInstruction(store, (), ())) - return var - - def add_uninitialized_var(self, var: expr.Var, /): - """Add a variable with no initializer. - - In most cases, you should use :meth:`add_var` to initialize the variable. To use this - function, you must already hold a :class:`~.expr.Var` instance, as the use of the function - typically only makes sense in copying contexts. - - .. warning:: - - Qiskit makes no assertions about what an uninitialized variable will evaluate to at - runtime, and some hardware may reject this as an error. - - You should treat this function with caution, and as a low-level primitive that is useful - only in special cases of programmatically rebuilding two like circuits. - - Args: - var: the variable to add. - """ - # This function is deliberately meant to be a bit harder to find, to have a long descriptive - # name, and to be a bit less ergonomic than `add_var` (i.e. not allowing the (name, type) - # overload) to discourage people from using it when they should use `add_var`. - # - # This function exists so that there is a method to emulate `copy_empty_like`'s behaviour of - # adding uninitialised variables, which there's no obvious way around. We need to be sure - # that _some_ sort of handling of uninitialised variables is taken into account in our - # structures, so that doesn't become a huge edge case, even though we make no assertions - # about the _meaning_ if such an expression was run on hardware. - if self._control_flow_scopes: - raise CircuitError("cannot add an uninitialized variable in a control-flow scope") - if not var.standalone: - raise CircuitError("cannot add a variable wrapping a bit or register to a circuit") - self._builder_api.add_uninitialized_var(var) - - def add_capture(self, var: expr.Var): - """Add a variable to the circuit that it should capture from a scope it will be contained - within. - - This method requires a :class:`~.expr.Var` node to enforce that you've got a handle to one, - because you will need to declare the same variable using the same object into the outer - circuit. - - This is a low-level method, which is only really useful if you are manually constructing - control-flow operations. You typically will not need to call this method, assuming you - are using the builder interface for control-flow scopes (``with`` context-manager statements - for :meth:`if_test` and the other scoping constructs). The builder interface will - automatically make the inner scopes closures on your behalf by capturing any variables that - are used within them. - - Args: - var: the variable to capture from an enclosing scope. - - Raises: - CircuitError: if the variable cannot be created due to shadowing an existing variable. - """ - if self._control_flow_scopes: - # Allow manual capturing. Not sure why it'd be useful, but there's a clear expected - # behaviour here. - self._control_flow_scopes[-1].use_var(var) - return - if self._vars_input: - raise CircuitError( - "circuits with input variables cannot be enclosed, so cannot be closures" - ) - self._vars_capture[var.name] = self._prepare_new_var(var, None) - - @typing.overload - def add_input(self, name_or_var: str, type_: types.Type, /) -> expr.Var: - ... - - @typing.overload - def add_input(self, name_or_var: expr.Var, type_: None = None, /) -> expr.Var: - ... - - def add_input( # pylint: disable=missing-raises-doc - self, name_or_var: str | expr.Var, type_: types.Type | None = None, / - ) -> expr.Var: - """Register a variable as an input to the circuit. - - Args: - name_or_var: either a string name, or an existing :class:`~.expr.Var` node to use as the - input variable. - type_: if the name is given as a string, then this must be a :class:`~.types.Type` to - use for the variable. If the variable is given as an existing :class:`~.expr.Var`, - then this must not be given, and will instead be read from the object itself. - - Returns: - the variable created, or the same variable as was passed in. - - Raises: - CircuitError: if the variable cannot be created due to shadowing an existing variable. - """ - if self._control_flow_scopes: - raise CircuitError("cannot add an input variable in a control-flow scope") - if self._vars_capture: - raise CircuitError("circuits to be enclosed with captures cannot have input variables") - if isinstance(name_or_var, expr.Var) and type_ is not None: - raise ValueError("cannot give an explicit type with an existing Var") - var = self._prepare_new_var(name_or_var, type_) - self._vars_input[var.name] = var - return var - def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: """Add registers.""" if not regs: @@ -2533,14 +2112,6 @@ def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit": * global phase * all the qubits and clbits, including the registers - .. warning:: - - If the circuit contains any local variable declarations (those added by the - ``declarations`` argument to the circuit constructor, or using :meth:`add_var`), they - will be **uninitialized** in the output circuit. You will need to manually add store - instructions for them (see :class:`.Store` and :meth:`.QuantumCircuit.store`) to - initialize them. - Args: name (str): Name for the copied circuit. If None, then the name stays the same. @@ -2560,13 +2131,6 @@ def copy_empty_like(self, name: str | None = None) -> "QuantumCircuit": cpy._qubit_indices = self._qubit_indices.copy() cpy._clbit_indices = self._clbit_indices.copy() - # Note that this causes the local variables to be uninitialised, because the stores are not - # copied. This can leave the circuit in a potentially dangerous state for users if they - # don't re-add initialiser stores. - cpy._vars_local = self._vars_local.copy() - cpy._vars_input = self._vars_input.copy() - cpy._vars_capture = self._vars_capture.copy() - cpy._parameter_table = ParameterTable() for parameter in getattr(cpy.global_phase, "parameters", ()): cpy._parameter_table[parameter] = ParameterReferences( @@ -2626,31 +2190,6 @@ def reset(self, qubit: QubitSpecifier) -> InstructionSet: return self.append(Reset(), [qubit], []) - def store(self, lvalue: typing.Any, rvalue: typing.Any, /) -> InstructionSet: - """Store the result of the given runtime classical expression ``rvalue`` in the memory - location defined by ``lvalue``. - - Typically ``lvalue`` will be a :class:`~.expr.Var` node and ``rvalue`` will be some - :class:`~.expr.Expr` to write into it, but anything that :func:`.expr.lift` can raise to an - :class:`~.expr.Expr` is permissible in both places, and it will be called on them. - - Args: - lvalue: a valid specifier for a memory location in the circuit. This will typically be - a :class:`~.expr.Var` node, but you can also write to :class:`.Clbit` or - :class:`.ClassicalRegister` memory locations if your hardware supports it. The - memory location must already be present in the circuit. - rvalue: a runtime classical expression whose result should be written into the given - memory location. - - .. seealso:: - :class:`~.circuit.Store` - The backing :class:`~.circuit.Instruction` class that represents this operation. - - :meth:`add_var` - Create a new variable in the circuit that can be written to with this method. - """ - return self.append(Store(expr.lift(lvalue), expr.lift(rvalue)), (), ()) - def measure(self, qubit: QubitSpecifier, cbit: ClbitSpecifier) -> InstructionSet: r"""Measure a quantum bit (``qubit``) in the Z basis into a classical bit (``cbit``). @@ -5471,24 +5010,6 @@ def resolve_classical_resource(self, specifier): raise CircuitError(f"Classical bit index {specifier} is out-of-range.") from None raise CircuitError(f"Unknown classical resource specifier: '{specifier}'.") - def add_uninitialized_var(self, var): - var = self.circuit._prepare_new_var(var, None) - self.circuit._vars_local[var.name] = var - - def remove_var(self, var): - self.circuit._vars_local.pop(var.name) - - def get_var(self, name): - if (out := self.circuit._vars_local.get(name)) is not None: - return out - if (out := self.circuit._vars_capture.get(name)) is not None: - return out - return self.circuit._vars_input.get(name) - - def use_var(self, var): - if self.get_var(var.name) != var: - raise CircuitError(f"'{var}' is not present in this circuit") - def _validate_expr(circuit_scope: CircuitScopeInterface, node: expr.Expr) -> expr.Expr: # This takes the `circuit_scope` object as an argument rather than being a circuit method and diff --git a/qiskit/circuit/store.py b/qiskit/circuit/store.py deleted file mode 100644 index 100fe0e629b9..000000000000 --- a/qiskit/circuit/store.py +++ /dev/null @@ -1,87 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""The 'Store' operation.""" - -from __future__ import annotations - -import typing - -from .exceptions import CircuitError -from .classical import expr, types -from .instruction import Instruction - - -def _handle_equal_types(lvalue: expr.Expr, rvalue: expr.Expr, /) -> tuple[expr.Expr, expr.Expr]: - return lvalue, rvalue - - -def _handle_implicit_cast(lvalue: expr.Expr, rvalue: expr.Expr, /) -> tuple[expr.Expr, expr.Expr]: - return lvalue, expr.Cast(rvalue, lvalue.type, implicit=True) - - -def _requires_lossless_cast(lvalue: expr.Expr, rvalue: expr.Expr, /) -> typing.NoReturn: - raise CircuitError(f"an explicit cast is required from '{rvalue.type}' to '{lvalue.type}'") - - -def _requires_dangerous_cast(lvalue: expr.Expr, rvalue: expr.Expr, /) -> typing.NoReturn: - raise CircuitError( - f"an explicit cast is required from '{rvalue.type}' to '{lvalue.type}', which may be lossy" - ) - - -def _no_cast_possible(lvalue: expr.Expr, rvalue: expr.Expr) -> typing.NoReturn: - raise CircuitError(f"no cast is possible from '{rvalue.type}' to '{lvalue.type}'") - - -_HANDLE_CAST = { - types.CastKind.EQUAL: _handle_equal_types, - types.CastKind.IMPLICIT: _handle_implicit_cast, - types.CastKind.LOSSLESS: _requires_lossless_cast, - types.CastKind.DANGEROUS: _requires_dangerous_cast, - types.CastKind.NONE: _no_cast_possible, -} - - -class Store(Instruction): - """A manual storage of some classical value to a classical memory location. - - This is a low-level primitive of the classical-expression handling (similar to how - :class:`~.circuit.Measure` is a primitive for quantum measurement), and is not safe for - subclassing. It is likely to become a special-case instruction in later versions of Qiskit - circuit and compiler internal representations.""" - - def __init__(self, lvalue: expr.Expr, rvalue: expr.Expr): - if not expr.is_lvalue(lvalue): - raise CircuitError(f"'{lvalue}' is not an l-value") - - cast_kind = types.cast_kind(rvalue.type, lvalue.type) - if (handler := _HANDLE_CAST.get(cast_kind)) is None: - raise RuntimeError(f"unhandled cast kind required: {cast_kind}") - lvalue, rvalue = handler(lvalue, rvalue) - - super().__init__("store", 0, 0, [lvalue, rvalue]) - - @property - def lvalue(self): - """Get the l-value :class:`~.expr.Expr` node that is being stored to.""" - return self.params[0] - - @property - def rvalue(self): - """Get the r-value :class:`~.expr.Expr` node that is being written into the l-value.""" - return self.params[1] - - def c_if(self, classical, val): - raise NotImplementedError( - "stores cannot be conditioned with `c_if`; use a full `if_test` context instead" - ) diff --git a/releasenotes/notes/classical-store-e64ee1286219a862.yaml b/releasenotes/notes/classical-store-e64ee1286219a862.yaml deleted file mode 100644 index 729c70a6989c..000000000000 --- a/releasenotes/notes/classical-store-e64ee1286219a862.yaml +++ /dev/null @@ -1,56 +0,0 @@ ---- -features: - - | - A :class:`.QuantumCircuit` can now contain typed classical variables:: - - from qiskit.circuit import QuantumCircuit, ClassicalRegister, QuantumRegister - from qiskit.circuit.classical import expr, types - - qr = QuantumRegister(2, "q") - cr = ClassicalRegister(2, "c") - qc = QuantumCircuit(qr, cr) - # Add two input variables to the circuit with different types. - a = qc.add_input("a", types.Bool()) - mask = qc.add_input("mask", types.Uint(2)) - - # Test whether the input variable was true at runtime. - with qc.if_test(a) as else_: - qc.x(0) - with else_: - qc.h(0) - - qc.cx(0, 1) - qc.measure(qr, cr) - - # Add a typed variable manually, initialized to the same value as the classical register. - b = qc.add_var("b", expr.lift(cr)) - - qc.reset([0, 1]) - qc.h(0) - qc.cx(0, 1) - qc.measure(qr, cr) - - # Store some calculated value into the `b` variable. - qc.store(b, expr.bit_and(b, cr)) - # Test whether we had equality, up to a mask. - with qc.if_test(expr.equal(expr.bit_and(b, mask), mask)): - qc.x(0) - - These variables can be specified either as *inputs* to the circuit, or as scoped variables. - The circuit object does not yet have support for representing typed classical-variable *outputs*, - but this will be added later when hardware and the result interfaces are in more of a position - to support it. Circuits that represent a block of an inner scope may also capture variables - from outer scopes. - - A variable is a :class:`.Var` node, which can now contain an arbitrary type, and represents a - unique memory location within its live range when added to a circuit. These can be constructed - in a circuit using :meth:`.QuantumCircuit.add_var` and :meth:`~.QuantumCircuit.add_input`, or - at a lower level using :meth:`.Var.new`. - - Variables can be manually stored to, using the :class:`.Store` instruction and its corresponding - circuit method :meth:`.QuantumCircuit.store`. This includes writing to :class:`.Clbit` and - :class:`.ClassicalRegister` instances wrapped in :class:`.Var` nodes. - - Variables can be used wherever classical expressions (see :mod:`qiskit.circuit.classical.expr`) - are valid. Currently this is the target expressions of control-flow operations, though we plan - to expand this to gate parameters in the future, as the type and expression system are expanded. diff --git a/releasenotes/notes/expr-var-standalone-2c1116583a2be9fd.yaml b/releasenotes/notes/expr-var-standalone-2c1116583a2be9fd.yaml index 71ec0320032e..c4f463f4a6bf 100644 --- a/releasenotes/notes/expr-var-standalone-2c1116583a2be9fd.yaml +++ b/releasenotes/notes/expr-var-standalone-2c1116583a2be9fd.yaml @@ -2,5 +2,6 @@ features: - | :class:`~.expr.Var` nodes now have a :attr:`.Var.standalone` property to quickly query whether - they are new-style memory-owning variables, or whether they wrap old-style classical memory in - the form of a :class:`.Clbit` or :class:`.ClassicalRegister`. + they own their own memory, or if they wrap old-style classical memory in the form of a + :class:`.Clbit` or :class:`.ClassicalRegister`. This currently always returns ``False`` (i.e. + all :class:`~.expr.Var` nodes *do* wrap bits or registers), but will expand in the future. diff --git a/test/python/circuit/classical/test_expr_helpers.py b/test/python/circuit/classical/test_expr_helpers.py index 31b4d7028a8b..f7b420c07144 100644 --- a/test/python/circuit/classical/test_expr_helpers.py +++ b/test/python/circuit/classical/test_expr_helpers.py @@ -115,30 +115,3 @@ def always_equal(_): # ``True`` instead. self.assertFalse(expr.structurally_equivalent(left, right, not_handled, not_handled)) self.assertTrue(expr.structurally_equivalent(left, right, always_equal, always_equal)) - - -@ddt.ddt -class TestIsLValue(QiskitTestCase): - @ddt.data( - expr.Var.new("a", types.Bool()), - expr.Var.new("b", types.Uint(8)), - expr.Var(Clbit(), types.Bool()), - expr.Var(ClassicalRegister(8, "cr"), types.Uint(8)), - ) - def test_happy_cases(self, lvalue): - self.assertTrue(expr.is_lvalue(lvalue)) - - @ddt.data( - expr.Value(3, types.Uint(2)), - expr.Value(False, types.Bool()), - expr.Cast(expr.Var.new("a", types.Uint(2)), types.Uint(8)), - expr.Unary(expr.Unary.Op.LOGIC_NOT, expr.Var.new("a", types.Bool()), types.Bool()), - expr.Binary( - expr.Binary.Op.LOGIC_AND, - expr.Var.new("a", types.Bool()), - expr.Var.new("b", types.Bool()), - types.Bool(), - ), - ) - def test_bad_cases(self, not_an_lvalue): - self.assertFalse(expr.is_lvalue(not_an_lvalue)) diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index f8c1277cd0f4..34ad2cf4295a 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -14,7 +14,6 @@ import copy import pickle -import uuid import ddt @@ -58,52 +57,8 @@ def test_expr_can_be_cloned(self, obj): self.assertEqual(obj, copy.deepcopy(obj)) self.assertEqual(obj, pickle.loads(pickle.dumps(obj))) - def test_var_equality(self): - """Test that various types of :class:`.expr.Var` equality work as expected both in equal and - unequal cases.""" - var_a_bool = expr.Var.new("a", types.Bool()) - self.assertEqual(var_a_bool, var_a_bool) - - # Allocating a new variable should not compare equal, despite the name match. A semantic - # equality checker can choose to key these variables on only their names and types, if it - # knows that that check is valid within the semantic context. - self.assertNotEqual(var_a_bool, expr.Var.new("a", types.Bool())) - - # Manually constructing the same object with the same UUID should cause it compare equal, - # though, for serialisation ease. - self.assertEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="a")) - - # This is a badly constructed variable because it's using a different type to refer to the - # same storage location (the UUID) as another variable. It is an IR error to generate this - # sort of thing, but we can't fully be responsible for that and a pass would need to go out - # of its way to do this incorrectly, but we can still ensure that the direct equality check - # would spot the error. - self.assertNotEqual( - var_a_bool, expr.Var(var_a_bool.var, types.Uint(8), name=var_a_bool.name) - ) - - # This is also badly constructed because it uses a different name to refer to the "same" - # storage location. - self.assertNotEqual(var_a_bool, expr.Var(var_a_bool.var, types.Bool(), name="b")) - - # Obviously, two variables of different types and names should compare unequal. - self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(8))) - # As should two variables of the same name but different storage locations and types. - self.assertNotEqual(expr.Var.new("a", types.Bool()), expr.Var.new("a", types.Uint(8))) - - def test_var_uuid_clone(self): - """Test that :class:`.expr.Var` instances that have an associated UUID and name roundtrip - through pickle and copy operations to produce values that compare equal.""" - var_a_u8 = expr.Var.new("a", types.Uint(8)) - - self.assertEqual(var_a_u8, pickle.loads(pickle.dumps(var_a_u8))) - self.assertEqual(var_a_u8, copy.copy(var_a_u8)) - self.assertEqual(var_a_u8, copy.deepcopy(var_a_u8)) - def test_var_standalone(self): """Test that the ``Var.standalone`` property is set correctly.""" - self.assertTrue(expr.Var.new("a", types.Bool()).standalone) - self.assertTrue(expr.Var.new("a", types.Uint(8)).standalone) self.assertFalse(expr.Var(Clbit(), types.Bool()).standalone) self.assertFalse(expr.Var(ClassicalRegister(8, "cr"), types.Uint(8)).standalone) @@ -112,16 +67,12 @@ def test_var_hashable(self): cregs = [ClassicalRegister(2, "cr1"), ClassicalRegister(2, "cr2")] vars_ = [ - expr.Var.new("a", types.Bool()), - expr.Var.new("b", types.Uint(16)), expr.Var(clbits[0], types.Bool()), expr.Var(clbits[1], types.Bool()), expr.Var(cregs[0], types.Uint(2)), expr.Var(cregs[1], types.Uint(2)), ] duplicates = [ - expr.Var(uuid.UUID(bytes=vars_[0].var.bytes), types.Bool(), name=vars_[0].name), - expr.Var(uuid.UUID(bytes=vars_[1].var.bytes), types.Uint(16), name=vars_[1].name), expr.Var(clbits[0], types.Bool()), expr.Var(clbits[1], types.Bool()), expr.Var(cregs[0], types.Uint(2)), diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index 1bd96149f347..9002e0e08460 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -19,7 +19,6 @@ from qiskit import BasicAer, ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import Gate, Instruction, Measure, Parameter, Barrier from qiskit.circuit.bit import Bit -from qiskit.circuit.classical import expr, types from qiskit.circuit.classicalregister import Clbit from qiskit.circuit.exceptions import CircuitError from qiskit.circuit.controlflow import IfElseOp @@ -411,77 +410,6 @@ def test_copy_empty_like_circuit(self): copied = qc.copy_empty_like("copy") self.assertEqual(copied.name, "copy") - def test_copy_variables(self): - """Test that a full copy of circuits including variables copies them across.""" - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(8)) - c = expr.Var.new("c", types.Bool()) - d = expr.Var.new("d", types.Uint(8)) - - qc = QuantumCircuit(inputs=[a], declarations=[(c, expr.lift(False))]) - copied = qc.copy() - self.assertEqual({a}, set(copied.iter_input_vars())) - self.assertEqual({c}, set(copied.iter_declared_vars())) - self.assertEqual( - [instruction.operation for instruction in qc], - [instruction.operation for instruction in copied.data], - ) - - # Check that the original circuit is not mutated. - copied.add_input(b) - copied.add_var(d, 0xFF) - self.assertEqual({a, b}, set(copied.iter_input_vars())) - self.assertEqual({c, d}, set(copied.iter_declared_vars())) - self.assertEqual({a}, set(qc.iter_input_vars())) - self.assertEqual({c}, set(qc.iter_declared_vars())) - - qc = QuantumCircuit(captures=[b], declarations=[(a, expr.lift(False)), (c, a)]) - copied = qc.copy() - self.assertEqual({b}, set(copied.iter_captured_vars())) - self.assertEqual({a, c}, set(copied.iter_declared_vars())) - self.assertEqual( - [instruction.operation for instruction in qc], - [instruction.operation for instruction in copied.data], - ) - - # Check that the original circuit is not mutated. - copied.add_capture(d) - self.assertEqual({b, d}, set(copied.iter_captured_vars())) - self.assertEqual({b}, set(qc.iter_captured_vars())) - - def test_copy_empty_variables(self): - """Test that an empty copy of circuits including variables copies them across, but does not - initialise them.""" - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(8)) - c = expr.Var.new("c", types.Bool()) - d = expr.Var.new("d", types.Uint(8)) - - qc = QuantumCircuit(inputs=[a], declarations=[(c, expr.lift(False))]) - copied = qc.copy_empty_like() - self.assertEqual({a}, set(copied.iter_input_vars())) - self.assertEqual({c}, set(copied.iter_declared_vars())) - self.assertEqual([], list(copied.data)) - - # Check that the original circuit is not mutated. - copied.add_input(b) - copied.add_var(d, 0xFF) - self.assertEqual({a, b}, set(copied.iter_input_vars())) - self.assertEqual({c, d}, set(copied.iter_declared_vars())) - self.assertEqual({a}, set(qc.iter_input_vars())) - self.assertEqual({c}, set(qc.iter_declared_vars())) - - qc = QuantumCircuit(captures=[b], declarations=[(a, expr.lift(False)), (c, a)]) - copied = qc.copy_empty_like() - self.assertEqual({b}, set(copied.iter_captured_vars())) - self.assertEqual({a, c}, set(copied.iter_declared_vars())) - self.assertEqual([], list(copied.data)) - - # Check that the original circuit is not mutated. - copied.add_capture(d) - self.assertEqual({b, d}, set(copied.iter_captured_vars())) - self.assertEqual({b}, set(qc.iter_captured_vars())) - def test_copy_empty_like_parametric_phase(self): """Test that the parameter table of an empty circuit remains valid after copying a circuit with a parametric global phase.""" diff --git a/test/python/circuit/test_circuit_vars.py b/test/python/circuit/test_circuit_vars.py deleted file mode 100644 index c09e4717af3f..000000000000 --- a/test/python/circuit/test_circuit_vars.py +++ /dev/null @@ -1,393 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring - -from qiskit.test import QiskitTestCase -from qiskit.circuit import QuantumCircuit, CircuitError, Clbit, ClassicalRegister -from qiskit.circuit.classical import expr, types - - -class TestCircuitVars(QiskitTestCase): - """Tests for variable-manipulation routines on circuits. More specific functionality is likely - tested in the suites of the specific methods.""" - - def test_initialise_inputs(self): - vars_ = [expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(16))] - qc = QuantumCircuit(inputs=vars_) - self.assertEqual(set(vars_), set(qc.iter_vars())) - self.assertEqual(qc.num_vars, len(vars_)) - self.assertEqual(qc.num_input_vars, len(vars_)) - self.assertEqual(qc.num_captured_vars, 0) - self.assertEqual(qc.num_declared_vars, 0) - - def test_initialise_captures(self): - vars_ = [expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Uint(16))] - qc = QuantumCircuit(captures=vars_) - self.assertEqual(set(vars_), set(qc.iter_vars())) - self.assertEqual(qc.num_vars, len(vars_)) - self.assertEqual(qc.num_input_vars, 0) - self.assertEqual(qc.num_captured_vars, len(vars_)) - self.assertEqual(qc.num_declared_vars, 0) - - def test_initialise_declarations_iterable(self): - vars_ = [ - (expr.Var.new("a", types.Bool()), expr.lift(True)), - (expr.Var.new("b", types.Uint(16)), expr.lift(0xFFFF)), - ] - qc = QuantumCircuit(declarations=vars_) - - self.assertEqual({var for var, _initialiser in vars_}, set(qc.iter_vars())) - self.assertEqual(qc.num_vars, len(vars_)) - self.assertEqual(qc.num_input_vars, 0) - self.assertEqual(qc.num_captured_vars, 0) - self.assertEqual(qc.num_declared_vars, len(vars_)) - operations = [ - (instruction.operation.name, instruction.operation.lvalue, instruction.operation.rvalue) - for instruction in qc.data - ] - self.assertEqual(operations, [("store", lvalue, rvalue) for lvalue, rvalue in vars_]) - - def test_initialise_declarations_mapping(self): - # Dictionary iteration order is guaranteed to be insertion order. - vars_ = { - expr.Var.new("a", types.Bool()): expr.lift(True), - expr.Var.new("b", types.Uint(16)): expr.lift(0xFFFF), - } - qc = QuantumCircuit(declarations=vars_) - - self.assertEqual(set(vars_), set(qc.iter_vars())) - operations = [ - (instruction.operation.name, instruction.operation.lvalue, instruction.operation.rvalue) - for instruction in qc.data - ] - self.assertEqual( - operations, [("store", lvalue, rvalue) for lvalue, rvalue in vars_.items()] - ) - - def test_initialise_declarations_dependencies(self): - """Test that the cirucit initialiser can take in declarations with dependencies between - them, provided they're specified in a suitable order.""" - a = expr.Var.new("a", types.Bool()) - vars_ = [ - (a, expr.lift(True)), - (expr.Var.new("b", types.Bool()), a), - ] - qc = QuantumCircuit(declarations=vars_) - - self.assertEqual({var for var, _initialiser in vars_}, set(qc.iter_vars())) - operations = [ - (instruction.operation.name, instruction.operation.lvalue, instruction.operation.rvalue) - for instruction in qc.data - ] - self.assertEqual(operations, [("store", lvalue, rvalue) for lvalue, rvalue in vars_]) - - def test_initialise_inputs_declarations(self): - a = expr.Var.new("a", types.Uint(16)) - b = expr.Var.new("b", types.Uint(16)) - b_init = expr.bit_and(a, 0xFFFF) - qc = QuantumCircuit(inputs=[a], declarations={b: b_init}) - - self.assertEqual({a}, set(qc.iter_input_vars())) - self.assertEqual({b}, set(qc.iter_declared_vars())) - self.assertEqual({a, b}, set(qc.iter_vars())) - self.assertEqual(qc.num_vars, 2) - self.assertEqual(qc.num_input_vars, 1) - self.assertEqual(qc.num_captured_vars, 0) - self.assertEqual(qc.num_declared_vars, 1) - operations = [ - (instruction.operation.name, instruction.operation.lvalue, instruction.operation.rvalue) - for instruction in qc.data - ] - self.assertEqual(operations, [("store", b, b_init)]) - - def test_initialise_captures_declarations(self): - a = expr.Var.new("a", types.Uint(16)) - b = expr.Var.new("b", types.Uint(16)) - b_init = expr.bit_and(a, 0xFFFF) - qc = QuantumCircuit(captures=[a], declarations={b: b_init}) - - self.assertEqual({a}, set(qc.iter_captured_vars())) - self.assertEqual({b}, set(qc.iter_declared_vars())) - self.assertEqual({a, b}, set(qc.iter_vars())) - self.assertEqual(qc.num_vars, 2) - self.assertEqual(qc.num_input_vars, 0) - self.assertEqual(qc.num_captured_vars, 1) - self.assertEqual(qc.num_declared_vars, 1) - operations = [ - (instruction.operation.name, instruction.operation.lvalue, instruction.operation.rvalue) - for instruction in qc.data - ] - self.assertEqual(operations, [("store", b, b_init)]) - - def test_add_uninitialized_var(self): - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit() - qc.add_uninitialized_var(a) - self.assertEqual({a}, set(qc.iter_vars())) - self.assertEqual([], list(qc.data)) - - def test_add_var_returns_good_var(self): - qc = QuantumCircuit() - a = qc.add_var("a", expr.lift(True)) - self.assertEqual(a.name, "a") - self.assertEqual(a.type, types.Bool()) - - b = qc.add_var("b", expr.Value(0xFF, types.Uint(8))) - self.assertEqual(b.name, "b") - self.assertEqual(b.type, types.Uint(8)) - - def test_add_var_returns_input(self): - """Test that the `Var` returned by `add_var` is the same as the input if `Var`.""" - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit() - a_other = qc.add_var(a, expr.lift(True)) - self.assertIs(a, a_other) - - def test_add_input_returns_good_var(self): - qc = QuantumCircuit() - a = qc.add_input("a", types.Bool()) - self.assertEqual(a.name, "a") - self.assertEqual(a.type, types.Bool()) - - b = qc.add_input("b", types.Uint(8)) - self.assertEqual(b.name, "b") - self.assertEqual(b.type, types.Uint(8)) - - def test_add_input_returns_input(self): - """Test that the `Var` returned by `add_input` is the same as the input if `Var`.""" - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit() - a_other = qc.add_input(a) - self.assertIs(a, a_other) - - def test_cannot_have_both_inputs_and_captures(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - - with self.assertRaisesRegex(CircuitError, "circuits with input.*cannot be closures"): - QuantumCircuit(inputs=[a], captures=[b]) - - qc = QuantumCircuit(inputs=[a]) - with self.assertRaisesRegex(CircuitError, "circuits with input.*cannot be closures"): - qc.add_capture(b) - - qc = QuantumCircuit(captures=[a]) - with self.assertRaisesRegex(CircuitError, "circuits to be enclosed.*cannot have input"): - qc.add_input(b) - - def test_cannot_add_cyclic_declaration(self): - a = expr.Var.new("a", types.Bool()) - with self.assertRaisesRegex(CircuitError, "not present in this circuit"): - QuantumCircuit(declarations=[(a, a)]) - - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "not present in this circuit"): - qc.add_var(a, a) - - def test_initialise_inputs_equal_to_add_input(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(16)) - - qc_init = QuantumCircuit(inputs=[a, b]) - qc_manual = QuantumCircuit() - qc_manual.add_input(a) - qc_manual.add_input(b) - self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars())) - - qc_manual = QuantumCircuit() - a = qc_manual.add_input("a", types.Bool()) - b = qc_manual.add_input("b", types.Uint(16)) - qc_init = QuantumCircuit(inputs=[a, b]) - self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars())) - - def test_initialise_captures_equal_to_add_capture(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(16)) - - qc_init = QuantumCircuit(captures=[a, b]) - qc_manual = QuantumCircuit() - qc_manual.add_capture(a) - qc_manual.add_capture(b) - self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars())) - - def test_initialise_declarations_equal_to_add_var(self): - a = expr.Var.new("a", types.Bool()) - a_init = expr.lift(False) - b = expr.Var.new("b", types.Uint(16)) - b_init = expr.lift(0xFFFF) - - qc_init = QuantumCircuit(declarations=[(a, a_init), (b, b_init)]) - qc_manual = QuantumCircuit() - qc_manual.add_var(a, a_init) - qc_manual.add_var(b, b_init) - self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars())) - self.assertEqual(qc_init.data, qc_manual.data) - - qc_manual = QuantumCircuit() - a = qc_manual.add_var("a", a_init) - b = qc_manual.add_var("b", b_init) - qc_init = QuantumCircuit(declarations=[(a, a_init), (b, b_init)]) - self.assertEqual(list(qc_init.iter_vars()), list(qc_manual.iter_vars())) - self.assertEqual(qc_init.data, qc_manual.data) - - def test_cannot_shadow_vars(self): - """Test that exact duplicate ``Var`` nodes within different combinations of the inputs are - detected and rejected.""" - a = expr.Var.new("a", types.Bool()) - a_init = expr.lift(True) - with self.assertRaisesRegex(CircuitError, "already present"): - QuantumCircuit(inputs=[a, a]) - with self.assertRaisesRegex(CircuitError, "already present"): - QuantumCircuit(captures=[a, a]) - with self.assertRaisesRegex(CircuitError, "already present"): - QuantumCircuit(declarations=[(a, a_init), (a, a_init)]) - with self.assertRaisesRegex(CircuitError, "already present"): - QuantumCircuit(inputs=[a], declarations=[(a, a_init)]) - with self.assertRaisesRegex(CircuitError, "already present"): - QuantumCircuit(captures=[a], declarations=[(a, a_init)]) - - def test_cannot_shadow_names(self): - """Test that exact duplicate ``Var`` nodes within different combinations of the inputs are - detected and rejected.""" - a_bool1 = expr.Var.new("a", types.Bool()) - a_bool2 = expr.Var.new("a", types.Bool()) - a_uint = expr.Var.new("a", types.Uint(16)) - a_bool_init = expr.lift(True) - a_uint_init = expr.lift(0xFFFF) - - tests = [ - ((a_bool1, a_bool_init), (a_bool2, a_bool_init)), - ((a_bool1, a_bool_init), (a_uint, a_uint_init)), - ] - for (left, left_init), (right, right_init) in tests: - with self.assertRaisesRegex(CircuitError, "its name shadows"): - QuantumCircuit(inputs=(left, right)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - QuantumCircuit(captures=(left, right)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - QuantumCircuit(declarations=[(left, left_init), (right, right_init)]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - QuantumCircuit(inputs=[left], declarations=[(right, right_init)]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - QuantumCircuit(captures=[left], declarations=[(right, right_init)]) - - qc = QuantumCircuit(inputs=[left]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_input(right) - qc = QuantumCircuit(inputs=[left]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_var(right, right_init) - - qc = QuantumCircuit(captures=[left]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_capture(right) - qc = QuantumCircuit(captures=[left]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_var(right, right_init) - - qc = QuantumCircuit(inputs=[left]) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_var(right, right_init) - - qc = QuantumCircuit() - qc.add_var("a", expr.lift(True)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_var("a", expr.lift(True)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - qc.add_var("a", expr.lift(0xFF)) - - def test_cannot_add_vars_wrapping_clbits(self): - a = expr.Var(Clbit(), types.Bool()) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(inputs=[a]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_input(a) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(captures=[a]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_capture(a) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(declarations=[(a, expr.lift(True))]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_var(a, expr.lift(True)) - - def test_cannot_add_vars_wrapping_cregs(self): - a = expr.Var(ClassicalRegister(8, "cr"), types.Uint(8)) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(inputs=[a]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_input(a) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(captures=[a]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_capture(a) - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - QuantumCircuit(declarations=[(a, expr.lift(0xFF))]) - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "cannot add variables that wrap"): - qc.add_var(a, expr.lift(0xFF)) - - def test_get_var_success(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Uint(8)) - - qc = QuantumCircuit(inputs=[a], declarations={b: expr.Value(0xFF, types.Uint(8))}) - self.assertIs(qc.get_var("a"), a) - self.assertIs(qc.get_var("b"), b) - - qc = QuantumCircuit(captures=[a, b]) - self.assertIs(qc.get_var("a"), a) - self.assertIs(qc.get_var("b"), b) - - qc = QuantumCircuit(declarations={a: expr.lift(True), b: expr.Value(0xFF, types.Uint(8))}) - self.assertIs(qc.get_var("a"), a) - self.assertIs(qc.get_var("b"), b) - - def test_get_var_missing(self): - qc = QuantumCircuit() - with self.assertRaises(KeyError): - qc.get_var("a") - - a = expr.Var.new("a", types.Bool()) - qc.add_input(a) - with self.assertRaises(KeyError): - qc.get_var("b") - - def test_get_var_default(self): - qc = QuantumCircuit() - self.assertIs(qc.get_var("a", None), None) - - missing = "default" - a = expr.Var.new("a", types.Bool()) - qc.add_input(a) - self.assertIs(qc.get_var("b", missing), missing) - self.assertIs(qc.get_var("b", a), a) - - def test_has_var(self): - a = expr.Var.new("a", types.Bool()) - self.assertFalse(QuantumCircuit().has_var("a")) - self.assertTrue(QuantumCircuit(inputs=[a]).has_var("a")) - self.assertTrue(QuantumCircuit(captures=[a]).has_var("a")) - self.assertTrue(QuantumCircuit(declarations={a: expr.lift(True)}).has_var("a")) - self.assertTrue(QuantumCircuit(inputs=[a]).has_var(a)) - self.assertTrue(QuantumCircuit(captures=[a]).has_var(a)) - self.assertTrue(QuantumCircuit(declarations={a: expr.lift(True)}).has_var(a)) - - # When giving an `Var`, the match must be exact, not just the name. - self.assertFalse(QuantumCircuit(inputs=[a]).has_var(expr.Var.new("a", types.Uint(8)))) - self.assertFalse(QuantumCircuit(inputs=[a]).has_var(expr.Var.new("a", types.Bool()))) diff --git a/test/python/circuit/test_control_flow.py b/test/python/circuit/test_control_flow.py index ecb5de96f793..e827b679a4a2 100644 --- a/test/python/circuit/test_control_flow.py +++ b/test/python/circuit/test_control_flow.py @@ -510,49 +510,6 @@ def test_switch_rejects_cases_after_default(self): with self.assertRaisesRegex(CircuitError, "cases after the default are unreachable"): SwitchCaseOp(creg, [(CASE_DEFAULT, case1), (1, case2)]) - def test_if_else_rejects_input_vars(self): - """Bodies must not contain input variables.""" - cond = (Clbit(), False) - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - bad_body = QuantumCircuit(inputs=[a]) - good_body = QuantumCircuit(captures=[a], declarations=[(b, expr.lift(False))]) - - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - IfElseOp(cond, bad_body, None) - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - IfElseOp(cond, bad_body, good_body) - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - IfElseOp(cond, good_body, bad_body) - - def test_while_rejects_input_vars(self): - """Bodies must not contain input variables.""" - cond = (Clbit(), False) - a = expr.Var.new("a", types.Bool()) - bad_body = QuantumCircuit(inputs=[a]) - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - WhileLoopOp(cond, bad_body) - - def test_for_rejects_input_vars(self): - """Bodies must not contain input variables.""" - a = expr.Var.new("a", types.Bool()) - bad_body = QuantumCircuit(inputs=[a]) - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - ForLoopOp(range(3), None, bad_body) - - def test_switch_rejects_input_vars(self): - """Bodies must not contain input variables.""" - target = ClassicalRegister(3, "cr") - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - bad_body = QuantumCircuit(inputs=[a]) - good_body = QuantumCircuit(captures=[a], declarations=[(b, expr.lift(False))]) - - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - SwitchCaseOp(target, [(0, bad_body)]) - with self.assertRaisesRegex(CircuitError, "cannot contain input variables"): - SwitchCaseOp(target, [(0, good_body), (1, bad_body)]) - @ddt class TestAddingControlFlowOperations(QiskitTestCase): @@ -917,148 +874,3 @@ def test_nested_parameters_can_be_assigned(self): ) self.assertEqual(assigned, expected) - - def test_can_add_op_with_captures_of_inputs(self): - """Test circuit methods can capture input variables.""" - outer = QuantumCircuit(1, 1) - a = outer.add_input("a", types.Bool()) - - inner = QuantumCircuit(1, 1, captures=[a]) - - outer.if_test((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.if_else((outer.clbits[0], False), inner.copy(), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - outer.while_loop((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "while_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.for_loop(range(3), None, inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "for_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.switch(outer.clbits[0], [(False, inner.copy()), (True, inner.copy())], [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "switch_case") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - def test_can_add_op_with_captures_of_captures(self): - """Test circuit methods can capture captured variables.""" - outer = QuantumCircuit(1, 1) - a = expr.Var.new("a", types.Bool()) - outer.add_capture(a) - - inner = QuantumCircuit(1, 1, captures=[a]) - - outer.if_test((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.if_else((outer.clbits[0], False), inner.copy(), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - outer.while_loop((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "while_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.for_loop(range(3), None, inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "for_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.switch(outer.clbits[0], [(False, inner.copy()), (True, inner.copy())], [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "switch_case") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - def test_can_add_op_with_captures_of_locals(self): - """Test circuit methods can capture declared variables.""" - outer = QuantumCircuit(1, 1) - a = outer.add_var("a", expr.lift(True)) - - inner = QuantumCircuit(1, 1, captures=[a]) - - outer.if_test((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.if_else((outer.clbits[0], False), inner.copy(), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "if_else") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - outer.while_loop((outer.clbits[0], False), inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "while_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.for_loop(range(3), None, inner.copy(), [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "for_loop") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - - outer.switch(outer.clbits[0], [(False, inner.copy()), (True, inner.copy())], [0], [0]) - added = outer.data[-1].operation - self.assertEqual(added.name, "switch_case") - self.assertEqual(set(added.blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(added.blocks[1].iter_captured_vars()), {a}) - - def test_cannot_capture_unknown_variables_methods(self): - """Control-flow operations should not be able to capture variables that don't exist in the - outer circuit.""" - outer = QuantumCircuit(1, 1) - - a = expr.Var.new("a", types.Bool()) - inner = QuantumCircuit(1, 1, captures=[a]) - - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.if_test((outer.clbits[0], False), inner.copy(), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.if_else((outer.clbits[0], False), inner.copy(), inner.copy(), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.while_loop((outer.clbits[0], False), inner.copy(), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.for_loop(range(3), None, inner.copy(), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.switch(outer.clbits[0], [(False, inner.copy()), (True, inner.copy())], [0], [0]) - - def test_cannot_capture_unknown_variables_append(self): - """Control-flow operations should not be able to capture variables that don't exist in the - outer circuit.""" - outer = QuantumCircuit(1, 1) - - a = expr.Var.new("a", types.Bool()) - inner = QuantumCircuit(1, 1, captures=[a]) - - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.append(IfElseOp((outer.clbits[0], False), inner.copy(), None), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.append(IfElseOp((outer.clbits[0], False), inner.copy(), inner.copy()), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.append(WhileLoopOp((outer.clbits[0], False), inner.copy()), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.append(ForLoopOp(range(3), None, inner.copy()), [0], [0]) - with self.assertRaisesRegex(CircuitError, "not in this circuit"): - outer.append( - SwitchCaseOp(outer.clbits[0], [(False, inner.copy()), (True, inner.copy())]), - [0], - [0], - ) diff --git a/test/python/circuit/test_control_flow_builders.py b/test/python/circuit/test_control_flow_builders.py index ce8088fcd26c..18df7fb6798f 100644 --- a/test/python/circuit/test_control_flow_builders.py +++ b/test/python/circuit/test_control_flow_builders.py @@ -27,7 +27,6 @@ QuantumCircuit, QuantumRegister, Qubit, - Store, ) from qiskit.circuit.classical import expr, types from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, WhileLoopOp, SwitchCaseOp, CASE_DEFAULT @@ -3000,251 +2999,6 @@ def test_global_phase_of_blocks(self): [i * math.pi / 7 for i in range(1, 7)], ) - def test_can_capture_input(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - base = QuantumCircuit(inputs=[a, b]) - with base.for_loop(range(3)): - base.store(a, expr.lift(True)) - self.assertEqual(set(base.data[-1].operation.blocks[0].iter_captured_vars()), {a}) - - def test_can_capture_declared(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - base = QuantumCircuit(declarations=[(a, expr.lift(False)), (b, expr.lift(True))]) - with base.if_test(expr.lift(False)): - base.store(a, expr.lift(True)) - self.assertEqual(set(base.data[-1].operation.blocks[0].iter_captured_vars()), {a}) - - def test_can_capture_capture(self): - # It's a bit wild to be manually building an outer circuit that's intended to be a subblock, - # but be using the control-flow builder interface internally, but eh, it should work. - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - base = QuantumCircuit(captures=[a, b]) - with base.while_loop(expr.lift(False)): - base.store(a, expr.lift(True)) - self.assertEqual(set(base.data[-1].operation.blocks[0].iter_captured_vars()), {a}) - - def test_can_capture_from_nested(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - c = expr.Var.new("c", types.Bool()) - base = QuantumCircuit(inputs=[a, b]) - with base.switch(expr.lift(False)) as case, case(case.DEFAULT): - base.add_var(c, expr.lift(False)) - with base.if_test(expr.lift(False)): - base.store(a, c) - outer_block = base.data[-1].operation.blocks[0] - inner_block = outer_block.data[-1].operation.blocks[0] - self.assertEqual(set(inner_block.iter_captured_vars()), {a, c}) - - # The containing block should have captured it as well, despite not using it explicitly. - self.assertEqual(set(outer_block.iter_captured_vars()), {a}) - self.assertEqual(set(outer_block.iter_declared_vars()), {c}) - - def test_can_manually_capture(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - base = QuantumCircuit(inputs=[a, b]) - with base.while_loop(expr.lift(False)): - # Why do this? Who knows, but it clearly has a well-defined meaning. - base.add_capture(a) - self.assertEqual(set(base.data[-1].operation.blocks[0].iter_captured_vars()), {a}) - - def test_later_blocks_do_not_inherit_captures(self): - """Neither 'if' nor 'switch' should have later blocks inherit the captures from the earlier - blocks, and the earlier blocks shouldn't be affected by later ones.""" - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - c = expr.Var.new("c", types.Bool()) - - base = QuantumCircuit(inputs=[a, b, c]) - with base.if_test(expr.lift(False)) as else_: - base.store(a, expr.lift(False)) - with else_: - base.store(b, expr.lift(False)) - blocks = base.data[-1].operation.blocks - self.assertEqual(set(blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(blocks[1].iter_captured_vars()), {b}) - - base = QuantumCircuit(inputs=[a, b, c]) - with base.switch(expr.lift(False)) as case: - with case(0): - base.store(a, expr.lift(False)) - with case(case.DEFAULT): - base.store(b, expr.lift(False)) - blocks = base.data[-1].operation.blocks - self.assertEqual(set(blocks[0].iter_captured_vars()), {a}) - self.assertEqual(set(blocks[1].iter_captured_vars()), {b}) - - def test_blocks_have_independent_declarations(self): - """The blocks of if and switch should be separate scopes for declarations.""" - b1 = expr.Var.new("b", types.Bool()) - b2 = expr.Var.new("b", types.Bool()) - self.assertNotEqual(b1, b2) - - base = QuantumCircuit() - with base.if_test(expr.lift(False)) as else_: - base.add_var(b1, expr.lift(False)) - with else_: - base.add_var(b2, expr.lift(False)) - blocks = base.data[-1].operation.blocks - self.assertEqual(set(blocks[0].iter_declared_vars()), {b1}) - self.assertEqual(set(blocks[1].iter_declared_vars()), {b2}) - - base = QuantumCircuit() - with base.switch(expr.lift(False)) as case: - with case(0): - base.add_var(b1, expr.lift(False)) - with case(case.DEFAULT): - base.add_var(b2, expr.lift(False)) - blocks = base.data[-1].operation.blocks - self.assertEqual(set(blocks[0].iter_declared_vars()), {b1}) - self.assertEqual(set(blocks[1].iter_declared_vars()), {b2}) - - def test_can_shadow_outer_name(self): - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - base = QuantumCircuit(inputs=[outer]) - with base.if_test(expr.lift(False)): - base.add_var(inner, expr.lift(True)) - block = base.data[-1].operation.blocks[0] - self.assertEqual(set(block.iter_declared_vars()), {inner}) - self.assertEqual(set(block.iter_captured_vars()), set()) - - def test_iterators_run_over_scope(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - c = expr.Var.new("c", types.Bool()) - d = expr.Var.new("d", types.Bool()) - - base = QuantumCircuit(inputs=[a, b, c]) - self.assertEqual(set(base.iter_input_vars()), {a, b, c}) - self.assertEqual(set(base.iter_declared_vars()), set()) - self.assertEqual(set(base.iter_captured_vars()), set()) - - with base.switch(expr.lift(3)) as case: - with case(0): - # Nothing here. - self.assertEqual(set(base.iter_vars()), set()) - self.assertEqual(set(base.iter_input_vars()), set()) - self.assertEqual(set(base.iter_declared_vars()), set()) - self.assertEqual(set(base.iter_captured_vars()), set()) - - # Capture a variable. - base.store(a, expr.lift(False)) - self.assertEqual(set(base.iter_captured_vars()), {a}) - - # Declare a variable. - base.add_var(d, expr.lift(False)) - self.assertEqual(set(base.iter_declared_vars()), {d}) - self.assertEqual(set(base.iter_vars()), {a, d}) - - with case(1): - # We should have reset. - self.assertEqual(set(base.iter_vars()), set()) - self.assertEqual(set(base.iter_input_vars()), set()) - self.assertEqual(set(base.iter_declared_vars()), set()) - self.assertEqual(set(base.iter_captured_vars()), set()) - - # Capture a variable. - base.store(b, expr.lift(False)) - self.assertEqual(set(base.iter_captured_vars()), {b}) - - # Capture some more in another scope. - with base.while_loop(expr.lift(False)): - self.assertEqual(set(base.iter_vars()), set()) - base.store(c, expr.lift(False)) - self.assertEqual(set(base.iter_captured_vars()), {c}) - - self.assertEqual(set(base.iter_captured_vars()), {b, c}) - self.assertEqual(set(base.iter_vars()), {b, c}) - # And back to the outer scope. - self.assertEqual(set(base.iter_input_vars()), {a, b, c}) - self.assertEqual(set(base.iter_declared_vars()), set()) - self.assertEqual(set(base.iter_captured_vars()), set()) - - def test_get_var_respects_scope(self): - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - base = QuantumCircuit(inputs=[outer]) - self.assertEqual(base.get_var("a"), outer) - with base.if_test(expr.lift(False)) as else_: - # Before we've done anything, getting the variable should get the outer one. - self.assertEqual(base.get_var("a"), outer) - - # If we shadow it, we should get the shadowed one after. - base.add_var(inner, expr.lift(False)) - self.assertEqual(base.get_var("a"), inner) - with else_: - # In a new scope, we should see the outer one again. - self.assertEqual(base.get_var("a"), outer) - # ... until we shadow it. - base.add_var(inner, expr.lift(False)) - self.assertEqual(base.get_var("a"), inner) - self.assertEqual(base.get_var("a"), outer) - - def test_has_var_respects_scope(self): - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - base = QuantumCircuit(inputs=[outer]) - self.assertEqual(base.get_var("a"), outer) - with base.if_test(expr.lift(False)) as else_: - self.assertFalse(base.has_var("b")) - - # Before we've done anything, we should see the outer one. - self.assertTrue(base.has_var("a")) - self.assertTrue(base.has_var(outer)) - self.assertFalse(base.has_var(inner)) - - # If we shadow it, we should see the shadowed one after. - base.add_var(inner, expr.lift(False)) - self.assertTrue(base.has_var("a")) - self.assertFalse(base.has_var(outer)) - self.assertTrue(base.has_var(inner)) - with else_: - # In a new scope, we should see the outer one again. - self.assertTrue(base.has_var("a")) - self.assertTrue(base.has_var(outer)) - self.assertFalse(base.has_var(inner)) - - # ... until we shadow it. - base.add_var(inner, expr.lift(False)) - self.assertTrue(base.has_var("a")) - self.assertFalse(base.has_var(outer)) - self.assertTrue(base.has_var(inner)) - - self.assertTrue(base.has_var("a")) - self.assertTrue(base.has_var(outer)) - self.assertFalse(base.has_var(inner)) - - def test_store_to_clbit_captures_bit(self): - base = QuantumCircuit(1, 2) - with base.if_test(expr.lift(False)): - base.store(expr.lift(base.clbits[0]), expr.lift(True)) - - expected = QuantumCircuit(1, 2) - body = QuantumCircuit([expected.clbits[0]]) - body.store(expr.lift(expected.clbits[0]), expr.lift(True)) - expected.if_test(expr.lift(False), body, [], [0]) - - self.assertEqual(base, expected) - - def test_store_to_register_captures_register(self): - cr1 = ClassicalRegister(2, "cr1") - cr2 = ClassicalRegister(2, "cr2") - base = QuantumCircuit(cr1, cr2) - with base.if_test(expr.lift(False)): - base.store(expr.lift(cr1), expr.lift(3)) - - body = QuantumCircuit(cr1) - body.store(expr.lift(cr1), expr.lift(3)) - expected = QuantumCircuit(cr1, cr2) - expected.if_test(expr.lift(False), body, [], cr1[:]) - - self.assertEqual(base, expected) - @ddt.ddt class TestControlFlowBuildersFailurePaths(QiskitTestCase): @@ -3776,124 +3530,3 @@ def test_compose_new_invalid_within_builder(self): with outer.if_test((outer.clbits[0], 1)): with self.assertRaisesRegex(CircuitError, r"Cannot emit a new composed circuit.*"): outer.compose(inner, inplace=False) - - def test_cannot_capture_variable_not_in_scope(self): - a = expr.Var.new("a", types.Bool()) - - base = QuantumCircuit(1, 1) - with base.if_test((0, True)) as else_, self.assertRaisesRegex(CircuitError, "not in scope"): - base.store(a, expr.lift(False)) - with else_, self.assertRaisesRegex(CircuitError, "not in scope"): - base.store(a, expr.lift(False)) - - base.add_input(a) - with base.while_loop((0, True)), self.assertRaisesRegex(CircuitError, "not in scope"): - base.store(expr.Var.new("a", types.Bool()), expr.lift(False)) - - with base.for_loop(range(3)): - with base.switch(base.clbits[0]) as case, case(0): - with self.assertRaisesRegex(CircuitError, "not in scope"): - base.store(expr.Var.new("a", types.Bool()), expr.lift(False)) - - def test_cannot_add_existing_variable(self): - a = expr.Var.new("a", types.Bool()) - base = QuantumCircuit() - with base.if_test(expr.lift(False)) as else_: - base.add_var(a, expr.lift(False)) - with self.assertRaisesRegex(CircuitError, "already present"): - base.add_var(a, expr.lift(False)) - with else_: - base.add_var(a, expr.lift(False)) - with self.assertRaisesRegex(CircuitError, "already present"): - base.add_var(a, expr.lift(False)) - - def test_cannot_shadow_in_same_scope(self): - a = expr.Var.new("a", types.Bool()) - base = QuantumCircuit() - with base.switch(expr.lift(3)) as case: - with case(0): - base.add_var(a, expr.lift(False)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - base.add_var(a.name, expr.lift(False)) - with case(case.DEFAULT): - base.add_var(a, expr.lift(False)) - with self.assertRaisesRegex(CircuitError, "its name shadows"): - base.add_var(a.name, expr.lift(False)) - - def test_cannot_shadow_captured_variable(self): - """It shouldn't be possible to shadow a variable that has already been captured into the - block.""" - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - - base = QuantumCircuit(inputs=[outer]) - with base.while_loop(expr.lift(True)): - # Capture the outer. - base.store(outer, expr.lift(True)) - # Attempt to shadow it. - with self.assertRaisesRegex(CircuitError, "its name shadows"): - base.add_var(inner, expr.lift(False)) - - def test_cannot_use_outer_variable_after_shadow(self): - """If we've shadowed a variable, the outer one shouldn't be visible to us for use.""" - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - - base = QuantumCircuit(inputs=[outer]) - with base.for_loop(range(3)): - # Shadow the outer. - base.add_var(inner, expr.lift(False)) - with self.assertRaisesRegex(CircuitError, "cannot use.*shadowed"): - base.store(outer, expr.lift(True)) - - def test_cannot_use_beyond_outer_shadow(self): - outer = expr.Var.new("a", types.Bool()) - inner = expr.Var.new("a", types.Bool()) - base = QuantumCircuit(inputs=[outer]) - with base.while_loop(expr.lift(True)): - # Shadow 'outer' - base.add_var(inner, expr.lift(True)) - with base.switch(expr.lift(3)) as case, case(0): - with self.assertRaisesRegex(CircuitError, "not in scope"): - # Attempt to access the shadowed variable. - base.store(outer, expr.lift(False)) - - def test_exception_during_initialisation_does_not_add_variable(self): - uint_var = expr.Var.new("a", types.Uint(16)) - bool_expr = expr.Value(False, types.Bool()) - with self.assertRaises(CircuitError): - Store(uint_var, bool_expr) - base = QuantumCircuit() - with base.while_loop(expr.lift(False)): - # Should succeed. - b = base.add_var("b", expr.lift(False)) - try: - base.add_var(uint_var, bool_expr) - except CircuitError: - pass - # Should succeed. - c = base.add_var("c", expr.lift(False)) - local_vars = set(base.iter_vars()) - self.assertEqual(local_vars, {b, c}) - - def test_cannot_use_old_var_not_in_circuit(self): - base = QuantumCircuit() - with base.if_test(expr.lift(False)) as else_: - with self.assertRaisesRegex(CircuitError, "not present"): - base.store(expr.lift(Clbit()), expr.lift(False)) - with else_: - with self.assertRaisesRegex(CircuitError, "not present"): - with base.if_test(expr.equal(ClassicalRegister(2, "c"), 3)): - pass - - def test_cannot_add_input_in_scope(self): - base = QuantumCircuit() - with base.for_loop(range(3)): - with self.assertRaisesRegex(CircuitError, "cannot add an input variable"): - base.add_input("a", types.Bool()) - - def test_cannot_add_uninitialized_in_scope(self): - base = QuantumCircuit() - with base.for_loop(range(3)): - with self.assertRaisesRegex(CircuitError, "cannot add an uninitialized variable"): - base.add_uninitialized_var(expr.Var.new("a", types.Bool())) diff --git a/test/python/circuit/test_store.py b/test/python/circuit/test_store.py deleted file mode 100644 index 7977765d8e45..000000000000 --- a/test/python/circuit/test_store.py +++ /dev/null @@ -1,199 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring - -from qiskit.test import QiskitTestCase -from qiskit.circuit import Store, Clbit, CircuitError, QuantumCircuit, ClassicalRegister -from qiskit.circuit.classical import expr, types - - -class TestStoreInstruction(QiskitTestCase): - """Tests of the properties of the ``Store`` instruction itself.""" - - def test_happy_path_construction(self): - lvalue = expr.Var.new("a", types.Bool()) - rvalue = expr.lift(Clbit()) - constructed = Store(lvalue, rvalue) - self.assertIsInstance(constructed, Store) - self.assertEqual(constructed.lvalue, lvalue) - self.assertEqual(constructed.rvalue, rvalue) - - def test_implicit_cast(self): - lvalue = expr.Var.new("a", types.Bool()) - rvalue = expr.Var.new("b", types.Uint(8)) - constructed = Store(lvalue, rvalue) - self.assertIsInstance(constructed, Store) - self.assertEqual(constructed.lvalue, lvalue) - self.assertEqual(constructed.rvalue, expr.Cast(rvalue, types.Bool(), implicit=True)) - - def test_rejects_non_lvalue(self): - not_an_lvalue = expr.logic_and( - expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Bool()) - ) - rvalue = expr.lift(False) - with self.assertRaisesRegex(CircuitError, "not an l-value"): - Store(not_an_lvalue, rvalue) - - def test_rejects_explicit_cast(self): - lvalue = expr.Var.new("a", types.Uint(16)) - rvalue = expr.Var.new("b", types.Uint(8)) - with self.assertRaisesRegex(CircuitError, "an explicit cast is required"): - Store(lvalue, rvalue) - - def test_rejects_dangerous_cast(self): - lvalue = expr.Var.new("a", types.Uint(8)) - rvalue = expr.Var.new("b", types.Uint(16)) - with self.assertRaisesRegex(CircuitError, "an explicit cast is required.*may be lossy"): - Store(lvalue, rvalue) - - def test_rejects_c_if(self): - instruction = Store(expr.Var.new("a", types.Bool()), expr.Var.new("b", types.Bool())) - with self.assertRaises(NotImplementedError): - instruction.c_if(Clbit(), False) - - -class TestStoreCircuit(QiskitTestCase): - """Tests of the `QuantumCircuit.store` method and appends of `Store`.""" - - def test_produces_expected_operation(self): - a = expr.Var.new("a", types.Bool()) - value = expr.Value(True, types.Bool()) - - qc = QuantumCircuit(inputs=[a]) - qc.store(a, value) - self.assertEqual(qc.data[-1].operation, Store(a, value)) - - qc = QuantumCircuit(captures=[a]) - qc.store(a, value) - self.assertEqual(qc.data[-1].operation, Store(a, value)) - - qc = QuantumCircuit(declarations=[(a, expr.lift(False))]) - qc.store(a, value) - self.assertEqual(qc.data[-1].operation, Store(a, value)) - - def test_allows_stores_with_clbits(self): - clbits = [Clbit(), Clbit()] - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit(clbits, inputs=[a]) - qc.store(clbits[0], True) - qc.store(expr.Var(clbits[1], types.Bool()), a) - qc.store(clbits[0], clbits[1]) - qc.store(expr.lift(clbits[0]), expr.lift(clbits[1])) - qc.store(a, expr.lift(clbits[1])) - - expected = [ - Store(expr.lift(clbits[0]), expr.lift(True)), - Store(expr.lift(clbits[1]), a), - Store(expr.lift(clbits[0]), expr.lift(clbits[1])), - Store(expr.lift(clbits[0]), expr.lift(clbits[1])), - Store(a, expr.lift(clbits[1])), - ] - actual = [instruction.operation for instruction in qc.data] - self.assertEqual(actual, expected) - - def test_allows_stores_with_cregs(self): - cregs = [ClassicalRegister(8, "cr1"), ClassicalRegister(8, "cr2")] - a = expr.Var.new("a", types.Uint(8)) - qc = QuantumCircuit(*cregs, captures=[a]) - qc.store(cregs[0], 0xFF) - qc.store(expr.Var(cregs[1], types.Uint(8)), a) - qc.store(cregs[0], cregs[1]) - qc.store(expr.lift(cregs[0]), expr.lift(cregs[1])) - qc.store(a, cregs[1]) - - expected = [ - Store(expr.lift(cregs[0]), expr.lift(0xFF)), - Store(expr.lift(cregs[1]), a), - Store(expr.lift(cregs[0]), expr.lift(cregs[1])), - Store(expr.lift(cregs[0]), expr.lift(cregs[1])), - Store(a, expr.lift(cregs[1])), - ] - actual = [instruction.operation for instruction in qc.data] - self.assertEqual(actual, expected) - - def test_lifts_values(self): - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit(captures=[a]) - qc.store(a, True) - self.assertEqual(qc.data[-1].operation, Store(a, expr.lift(True))) - - b = expr.Var.new("b", types.Uint(16)) - qc.add_capture(b) - qc.store(b, 0xFFFF) - self.assertEqual(qc.data[-1].operation, Store(b, expr.lift(0xFFFF))) - - def test_rejects_vars_not_in_circuit(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - - qc = QuantumCircuit() - with self.assertRaisesRegex(CircuitError, "'a'.*not present"): - qc.store(expr.Var.new("a", types.Bool()), True) - - # Not the same 'a' - qc.add_input(a) - with self.assertRaisesRegex(CircuitError, "'a'.*not present"): - qc.store(expr.Var.new("a", types.Bool()), True) - with self.assertRaisesRegex(CircuitError, "'b'.*not present"): - qc.store(a, b) - - def test_rejects_bits_not_in_circuit(self): - a = expr.Var.new("a", types.Bool()) - clbit = Clbit() - qc = QuantumCircuit(captures=[a]) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(clbit, False) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(clbit, a) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(a, clbit) - - def test_rejects_cregs_not_in_circuit(self): - a = expr.Var.new("a", types.Uint(8)) - creg = ClassicalRegister(8, "cr1") - qc = QuantumCircuit(captures=[a]) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(creg, 0xFF) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(creg, a) - with self.assertRaisesRegex(CircuitError, "not present"): - qc.store(a, creg) - - def test_rejects_non_lvalue(self): - a = expr.Var.new("a", types.Bool()) - b = expr.Var.new("b", types.Bool()) - qc = QuantumCircuit(inputs=[a, b]) - not_an_lvalue = expr.logic_and(a, b) - with self.assertRaisesRegex(CircuitError, "not an l-value"): - qc.store(not_an_lvalue, expr.lift(False)) - - def test_rejects_explicit_cast(self): - lvalue = expr.Var.new("a", types.Uint(16)) - rvalue = expr.Var.new("b", types.Uint(8)) - qc = QuantumCircuit(inputs=[lvalue, rvalue]) - with self.assertRaisesRegex(CircuitError, "an explicit cast is required"): - qc.store(lvalue, rvalue) - - def test_rejects_dangerous_cast(self): - lvalue = expr.Var.new("a", types.Uint(8)) - rvalue = expr.Var.new("b", types.Uint(16)) - qc = QuantumCircuit(inputs=[lvalue, rvalue]) - with self.assertRaisesRegex(CircuitError, "an explicit cast is required.*may be lossy"): - qc.store(lvalue, rvalue) - - def test_rejects_c_if(self): - a = expr.Var.new("a", types.Bool()) - qc = QuantumCircuit([Clbit()], inputs=[a]) - instruction_set = qc.store(a, True) - with self.assertRaises(NotImplementedError): - instruction_set.c_if(qc.clbits[0], False)