diff --git a/qiskit/circuit/classical/expr/constructors.py b/qiskit/circuit/classical/expr/constructors.py index 062795125ce4..96ad322befe6 100644 --- a/qiskit/circuit/classical/expr/constructors.py +++ b/qiskit/circuit/classical/expr/constructors.py @@ -80,11 +80,7 @@ def lift_legacy_condition( def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr: """Lift the given Python ``value`` to a :class:`~.expr.Value` or :class:`~.expr.Var`. - By default, lifted scalars are not const. To lift supported scalars to const-typed - expressions, specify `try_const=True`. - - If an explicit ``type`` is given, the typing in the output will reflect that, - including its const-ness. The ``try_const`` parameter is ignored when this is specified. + If an explicit ``type`` is given, the typing in the output will reflect that. Examples: Lifting simple circuit objects to be :class:`~.expr.Var` instances:: @@ -92,9 +88,9 @@ def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr: >>> from qiskit.circuit import Clbit, ClassicalRegister >>> from qiskit.circuit.classical import expr >>> expr.lift(Clbit()) - Var(, Bool(const=False)) + Var(, Bool()) >>> expr.lift(ClassicalRegister(3, "c")) - Var(ClassicalRegister(3, "c"), Uint(3, const=False)) + Var(ClassicalRegister(3, "c"), Uint(3)) The type of the return value can be influenced, if the given value could be interpreted losslessly as the given type (use :func:`cast` to perform a full set of casting @@ -103,7 +99,7 @@ def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr: >>> from qiskit.circuit import ClassicalRegister >>> from qiskit.circuit.classical import expr, types >>> expr.lift(ClassicalRegister(3, "c"), types.Uint(5)) - Var(ClassicalRegister(3, "c"), Uint(5, const=False)) + Var(ClassicalRegister(3, "c"), Uint(5)) >>> expr.lift(5, types.Uint(4)) Value(5, Uint(4)) """ @@ -114,12 +110,9 @@ def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr: from qiskit.circuit import Clbit, ClassicalRegister # pylint: disable=cyclic-import inferred: types.Type - if value is True or value is False: - inferred = types.Bool() - constructor = Value - elif isinstance(value, Clbit): + if value is True or value is False or isinstance(value, Clbit): inferred = types.Bool() - constructor = Var + constructor = Value if value is True or value is False else Var elif isinstance(value, ClassicalRegister): inferred = types.Uint(width=value.size) constructor = Var @@ -143,23 +136,14 @@ def lift(value: typing.Any, /, type: types.Type | None = None) -> Expr: def cast(operand: typing.Any, type: types.Type, /) -> Expr: """Create an explicit cast from the given value to the given type. - This can also be used to cast-away const status. - Examples: Add an explicit cast node that explicitly casts a higher precision type to a lower precision one:: >>> from qiskit.circuit.classical import expr, types - >>> value = expr.Value(5, types.Uint(32)) + >>> value = expr.value(5, types.Uint(32)) >>> expr.cast(value, types.Uint(8)) - Cast(Value(5, types.Uint(32, const=False)), types.Uint(8, const=False), implicit=False) - - Cast-away const status:: - - >>> from qiskit.circuit.classical import expr, types - >>> value = expr.Value(5, types.Uint(32, const=True)) - >>> expr.cast(value, types.Uint(32)) - Cast(Value(5, types.Uint(32, const=True)), types.Uint(32, const=False), implicit=False) + Cast(Value(5, types.Uint(32)), types.Uint(8), implicit=False) """ operand = lift(operand) if cast_kind(operand.type, type) is CastKind.NONE: @@ -177,8 +161,7 @@ def bit_not(operand: typing.Any, /) -> Expr: >>> from qiskit.circuit import ClassicalRegister >>> from qiskit.circuit.classical import expr >>> expr.bit_not(ClassicalRegister(3, "c")) - Unary(Unary.Op.BIT_NOT, \ -Var(ClassicalRegister(3, 'c'), Uint(3, const=False)), Uint(3, const=False)) + Unary(Unary.Op.BIT_NOT, Var(ClassicalRegister(3, 'c'), Uint(3)), Uint(3)) """ operand = lift(operand) if operand.type.kind not in (types.Bool, types.Uint): @@ -198,61 +181,43 @@ def logic_not(operand: typing.Any, /) -> Expr: >>> expr.logic_not(ClassicalRegister(3, "c")) Unary(\ Unary.Op.LOGIC_NOT, \ -Cast(Var(ClassicalRegister(3, 'c'), Uint(3, const=False)), \ -Bool(const=False), implicit=True), \ -Bool(const=False)) +Cast(Var(ClassicalRegister(3, 'c'), Uint(3)), Bool(), implicit=True), \ +Bool()) """ - operand = lift(operand) - operand = _coerce_lossless(operand, types.Bool()) + operand = _coerce_lossless(lift(operand), types.Bool()) return Unary(Unary.Op.LOGIC_NOT, operand, operand.type) def _lift_binary_operands(left: typing.Any, right: typing.Any) -> tuple[Expr, Expr]: """Lift two binary operands simultaneously, inferring the widths of integer literals in either - position to match the other operand. - - Const-ness is handled as follows: - * If neither operand is an expression, both are lifted as non-const. - * If only one operand is an expression, the other is lifted with the same const-ness, if possible. - Otherwise, the returned operands will have different const-ness, and thus may require a cast node - to be interoperable. - * If both operands are expressions, they are returned as-is, and may require a cast node. - """ + position to match the other operand.""" left_int = isinstance(left, int) and not isinstance(left, bool) right_int = isinstance(right, int) and not isinstance(right, bool) if not (left_int or right_int): - # They're either both bool, or neither are, so we lift them - # independently. left = lift(left) right = lift(right) elif not right_int: - # Left is an int. right = lift(right) if right.type.kind is types.Uint: if left.bit_length() > right.type.width: raise TypeError( f"integer literal '{left}' is wider than the other operand '{right}'" ) - # Left will share const-ness of right. left = Value(left, right.type) else: left = lift(left) elif not left_int: - # Right is an int. left = lift(left) if left.type.kind is types.Uint: if right.bit_length() > left.type.width: raise TypeError( f"integer literal '{right}' is wider than the other operand '{left}'" ) - # Right will share const-ness of left. right = Value(right, left.type) else: right = lift(right) else: # Both are `int`, so we take our best case to make things work. - # If the caller needs a const type, they should lift one side to - # a const type explicitly before calling this function. uint = types.Uint(max(left.bit_length(), right.bit_length(), 1)) left = Value(left, uint) right = Value(right, uint) @@ -265,15 +230,15 @@ def _binary_bitwise(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr: if left.type.kind is right.type.kind is types.Bool: type = types.Bool() elif left.type.kind is types.Uint and right.type.kind is types.Uint: - if left.type.width != right.type.width: + if left.type != right.type: raise TypeError( "binary bitwise operations are defined between unsigned integers of the same width," f" but got {left.type.width} and {right.type.width}." ) - type = types.Uint(width=left.type.width) + type = left.type else: raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'") - return Binary(op, _coerce_lossless(left, type), _coerce_lossless(right, type), type) + return Binary(op, left, right, type) def bit_and(left: typing.Any, right: typing.Any, /) -> Expr: @@ -288,9 +253,9 @@ def bit_and(left: typing.Any, right: typing.Any, /) -> Expr: >>> expr.bit_and(ClassicalRegister(3, "c"), 0b111) Binary(\ Binary.Op.BIT_AND, \ -Var(ClassicalRegister(3, 'c'), Uint(3, const=False)), \ -Value(7, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, 'c'), Uint(3)), \ +Value(7, Uint(3)), \ +Uint(3)) """ return _binary_bitwise(Binary.Op.BIT_AND, left, right) @@ -307,9 +272,9 @@ def bit_or(left: typing.Any, right: typing.Any, /) -> Expr: >>> expr.bit_or(ClassicalRegister(3, "c"), 0b101) Binary(\ Binary.Op.BIT_OR, \ -Var(ClassicalRegister(3, 'c'), Uint(3, const=False)), \ -Value(5, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, 'c'), Uint(3)), \ +Value(5, Uint(3)), \ +Uint(3)) """ return _binary_bitwise(Binary.Op.BIT_OR, left, right) @@ -326,20 +291,18 @@ def bit_xor(left: typing.Any, right: typing.Any, /) -> Expr: >>> expr.bit_xor(ClassicalRegister(3, "c"), 0b101) Binary(\ Binary.Op.BIT_XOR, \ -Var(ClassicalRegister(3, 'c'), Uint(3, const=False)), \ -Value(5, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, 'c'), Uint(3)), \ +Value(5, Uint(3)), \ +Uint(3)) """ return _binary_bitwise(Binary.Op.BIT_XOR, left, right) def _binary_logical(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr: - left = lift(left) - right = lift(right) - type = types.Bool() - left = _coerce_lossless(left, type) - right = _coerce_lossless(right, type) - return Binary(op, left, right, type) + bool_ = types.Bool() + left = _coerce_lossless(lift(left), bool_) + right = _coerce_lossless(lift(right), bool_) + return Binary(op, left, right, bool_) def logic_and(left: typing.Any, right: typing.Any, /) -> Expr: @@ -351,11 +314,8 @@ def logic_and(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit import Clbit >>> from qiskit.circuit.classical import expr - >>> expr.logic_and(Clbit(), Clbit()) - Binary(Binary.Op.LOGIC_AND, \ -Var(, Bool(const=False)), \ -Var(, Bool(const=False)), \ -Bool(const=False)) + >>> expr.logical_and(Clbit(), Clbit()) + Binary(Binary.Op.LOGIC_AND, Var(, Bool()), Var(, Bool()), Bool()) """ return _binary_logical(Binary.Op.LOGIC_AND, left, right) @@ -370,28 +330,17 @@ def logic_or(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit import Clbit >>> from qiskit.circuit.classical import expr >>> expr.logical_and(Clbit(), Clbit()) - Binary(Binary.Op.LOGIC_OR, \ -Var(, Bool(const=False)), \ -Var(, Bool(const=False)), \ -Bool(const=False)) + Binary(Binary.Op.LOGIC_OR, Var(, Bool()), Var(, Bool()), Bool()) """ return _binary_logical(Binary.Op.LOGIC_OR, left, right) def _equal_like(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr: left, right = _lift_binary_operands(left, right) - if ( - left.type.kind is not right.type.kind - or types.order(left.type, right.type) is types.Ordering.NONE - ): + if left.type.kind is not right.type.kind: raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'") type = types.greater(left.type, right.type) - return Binary( - op, - _coerce_lossless(left, type), - _coerce_lossless(right, type), - types.Bool(), - ) + return Binary(op, _coerce_lossless(left, type), _coerce_lossless(right, type), types.Bool()) def equal(left: typing.Any, right: typing.Any, /) -> Expr: @@ -405,9 +354,9 @@ def equal(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.equal(ClassicalRegister(3, "c"), 7) Binary(Binary.Op.EQUAL, \ -Var(ClassicalRegister(3, "c"), Uint(3, const=False)), \ -Value(7, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, "c"), Uint(3)), \ +Value(7, Uint(3)), \ +Uint(3)) """ return _equal_like(Binary.Op.EQUAL, left, right) @@ -423,28 +372,19 @@ def not_equal(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.not_equal(ClassicalRegister(3, "c"), 7) Binary(Binary.Op.NOT_EQUAL, \ -Var(ClassicalRegister(3, "c"), Uint(3, const=False)), \ -Value(7, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, "c"), Uint(3)), \ +Value(7, Uint(3)), \ +Uint(3)) """ return _equal_like(Binary.Op.NOT_EQUAL, left, right) def _binary_relation(op: Binary.Op, left: typing.Any, right: typing.Any) -> Expr: left, right = _lift_binary_operands(left, right) - if ( - left.type.kind is not right.type.kind - or left.type.kind is types.Bool - or types.order(left.type, right.type) is types.Ordering.NONE - ): + if left.type.kind is not right.type.kind or left.type.kind is types.Bool: raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'") type = types.greater(left.type, right.type) - return Binary( - op, - _coerce_lossless(left, type), - _coerce_lossless(right, type), - types.Bool(), - ) + return Binary(op, _coerce_lossless(left, type), _coerce_lossless(right, type), types.Bool()) def less(left: typing.Any, right: typing.Any, /) -> Expr: @@ -458,9 +398,9 @@ def less(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.less(ClassicalRegister(3, "c"), 5) Binary(Binary.Op.LESS, \ -Var(ClassicalRegister(3, "c"), Uint(3, const=False)), \ -Value(5, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, "c"), Uint(3)), \ +Value(5, Uint(3)), \ +Uint(3)) """ return _binary_relation(Binary.Op.LESS, left, right) @@ -476,9 +416,9 @@ def less_equal(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.less(ClassicalRegister(3, "a"), ClassicalRegister(3, "b")) Binary(Binary.Op.LESS_EQUAL, \ -Var(ClassicalRegister(3, "a"), Uint(3, const=False)), \ -Var(ClassicalRegister(3, "b"), Uint(3, const=False)), \ -Uint(3,const=False)) +Var(ClassicalRegister(3, "a"), Uint(3)), \ +Var(ClassicalRegister(3, "b"), Uint(3)), \ +Uint(3)) """ return _binary_relation(Binary.Op.LESS_EQUAL, left, right) @@ -494,9 +434,9 @@ def greater(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.less(ClassicalRegister(3, "c"), 5) Binary(Binary.Op.GREATER, \ -Var(ClassicalRegister(3, "c"), Uint(3, const=False)), \ -Value(5, Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, "c"), Uint(3)), \ +Value(5, Uint(3)), \ +Uint(3)) """ return _binary_relation(Binary.Op.GREATER, left, right) @@ -512,9 +452,9 @@ def greater_equal(left: typing.Any, right: typing.Any, /) -> Expr: >>> from qiskit.circuit.classical import expr >>> expr.less(ClassicalRegister(3, "a"), ClassicalRegister(3, "b")) Binary(Binary.Op.GREATER_EQUAL, \ -Var(ClassicalRegister(3, "a"), Uint(3, const=False)), \ -Var(ClassicalRegister(3, "b"), Uint(3, const=False)), \ -Uint(3, const=False)) +Var(ClassicalRegister(3, "a"), Uint(3)), \ +Var(ClassicalRegister(3, "b"), Uint(3)), \ +Uint(3)) """ return _binary_relation(Binary.Op.GREATER_EQUAL, left, right) @@ -531,12 +471,7 @@ def _shift_like( right = lift(right) if left.type.kind != types.Uint or right.type.kind != types.Uint: raise TypeError(f"invalid types for '{op}': '{left.type}' and '{right.type}'") - return Binary( - op, - left, - right, - types.Uint(width=left.type.width), - ) + return Binary(op, left, right, left.type) def shift_left(left: typing.Any, right: typing.Any, /, type: types.Type | None = None) -> Expr: @@ -552,17 +487,17 @@ def shift_left(left: typing.Any, right: typing.Any, /, type: types.Type | None = >>> a = expr.Var.new("a", types.Uint(8)) >>> expr.shift_left(a, 4) Binary(Binary.Op.SHIFT_LEFT, \ -Var(, Uint(8, const=False), name='a'), \ -Value(4, Uint(3, const=False)), \ -Uint(8, const=False)) +Var(, Uint(8), name='a'), \ +Value(4, Uint(3)), \ +Uint(8)) Shift an integer literal by a variable amount, coercing the type of the literal:: >>> expr.shift_left(3, a, types.Uint(16)) Binary(Binary.Op.SHIFT_LEFT, \ -Value(3, Uint(16, const=False)), \ -Var(, Uint(8, const=False), name='a'), \ -Uint(16, const=False)) +Value(3, Uint(16)), \ +Var(, Uint(8), name='a'), \ +Uint(16)) """ return _shift_like(Binary.Op.SHIFT_LEFT, left, right, type) @@ -580,9 +515,9 @@ def shift_right(left: typing.Any, right: typing.Any, /, type: types.Type | None >>> from qiskit.circuit.classical import expr >>> expr.shift_right(ClassicalRegister(8, "a"), 4) Binary(Binary.Op.SHIFT_RIGHT, \ -Var(ClassicalRegister(8, "a"), Uint(8, const=False)), \ -Value(4, Uint(3, const=False)), \ -Uint(8, const=False)) +Var(ClassicalRegister(8, "a"), Uint(8)), \ +Value(4, Uint(3)), \ +Uint(8)) """ return _shift_like(Binary.Op.SHIFT_RIGHT, left, right, type) @@ -599,13 +534,9 @@ def index(target: typing.Any, index: typing.Any, /) -> Expr: >>> from qiskit.circuit import ClassicalRegister >>> from qiskit.circuit.classical import expr >>> expr.index(ClassicalRegister(8, "a"), 3) - Index(\ -Var(ClassicalRegister(8, "a"), Uint(8, const=False)), \ -Value(3, Uint(2, const=False)), \ -Bool(const=False)) + Index(Var(ClassicalRegister(8, "a"), Uint(8)), Value(3, Uint(2)), Bool()) """ - target = lift(target) - index = lift(index) + target, index = lift(target), lift(index) if target.type.kind is not types.Uint or index.type.kind is not types.Uint: raise TypeError(f"invalid types for indexing: '{target.type}' and '{index.type}'") return Index(target, index, types.Bool()) diff --git a/qiskit/circuit/classical/types/__init__.py b/qiskit/circuit/classical/types/__init__.py index d10a4769ca50..ae38a0d97fb5 100644 --- a/qiskit/circuit/classical/types/__init__.py +++ b/qiskit/circuit/classical/types/__init__.py @@ -40,13 +40,6 @@ .. autoclass:: Bool .. autoclass:: Uint -All types have a :attr:`~Type.const` field to indicate their const-ness. When the result type of -an expression is constant, the expression is considered a constant expression. Constant -expressions can be used in certain contexts that aren't valid for runtime-initialized variables. -This is not to be confused with the concept of a ``const`` variable in languages like C, where -the variable has a well-defined but immutable storage location. Qiskit's definition of const-ness -is more similar to C++'s ``constexpr``. - Note that :class:`Uint` defines a family of types parametrized by their width; it is not one single type, which may be slightly different to the 'classical' programming languages you are used to. @@ -66,10 +59,7 @@ The type system is equipped with a partial ordering, where :math:`a < b` is interpreted as ":math:`a` is a strict subtype of :math:`b`". Note that the partial ordering is a subset of the directed graph that describes the allowed explicit casting operations between types. The partial -ordering defines when one type may be losslessly directly interpreted as another. - -When two types differ only in const-ness, the non-const version is considered to be the -"greater" of the two. +ordering defines when one type may be lossless directly interpreted as another. The low-level interface to querying the subtyping relationship is the :func:`order` function. diff --git a/qiskit/circuit/classical/types/ordering.py b/qiskit/circuit/classical/types/ordering.py index 7e232880d7f6..5a4365b8e14a 100644 --- a/qiskit/circuit/classical/types/ordering.py +++ b/qiskit/circuit/classical/types/ordering.py @@ -83,18 +83,10 @@ def order(left: Type, right: Type, /) -> Ordering: >>> types.order(types.Uint(8), types.Uint(16)) Ordering.LESS - Compare two :class:`Bool` types of differing const-ness:: - - >>> from qiskit.circuit.classical import types - >>> types.order(types.Bool(), types.Bool(const=True)) - Ordering.GREATER - Compare two types that have no ordering between them:: >>> types.order(types.Uint(8), types.Bool()) Ordering.NONE - >>> types.order(types.Uint(8), types.Uint(16, const=True)) - Ordering.NONE """ if (orderer := _ORDERERS.get((left.kind, right.kind))) is None: return Ordering.NONE @@ -119,8 +111,6 @@ def is_subtype(left: Type, right: Type, /, strict: bool = False) -> bool: True >>> types.is_subtype(types.Bool(), types.Bool(), strict=True) False - >>> types.is_subtype(types.Bool(const=True), types.Bool(), strict=True) - True """ order_ = order(left, right) return order_ is Ordering.LESS or (not strict and order_ is Ordering.EQUAL) @@ -144,8 +134,6 @@ def is_supertype(left: Type, right: Type, /, strict: bool = False) -> bool: True >>> types.is_supertype(types.Bool(), types.Bool(), strict=True) False - >>> types.is_supertype(types.Bool(), types.Bool(const=True), strict=True) - True """ order_ = order(left, right) return order_ is Ordering.GREATER or (not strict and order_ is Ordering.EQUAL) @@ -227,8 +215,6 @@ def cast_kind(from_: Type, to_: Type, /) -> CastKind: >>> types.cast_kind(types.Uint(8), types.Bool()) - >>> types.cast_kind(types.Uint(8, const=True), types.Uint(8)) - >>> types.cast_kind(types.Bool(), types.Uint(8)) >>> types.cast_kind(types.Uint(16), types.Uint(8)) @@ -236,5 +222,4 @@ def cast_kind(from_: Type, to_: Type, /) -> CastKind: """ if (coercer := _ALLOWED_CASTS.get((from_.kind, to_.kind))) is None: return CastKind.NONE - cast_kind_ = coercer(from_, to_) - return cast_kind_ + return coercer(from_, to_) diff --git a/qiskit/circuit/classical/types/types.py b/qiskit/circuit/classical/types/types.py index c65e063f52e3..d20e7b5fd746 100644 --- a/qiskit/circuit/classical/types/types.py +++ b/qiskit/circuit/classical/types/types.py @@ -19,22 +19,42 @@ from __future__ import annotations -__all__ = ["Type", "Bool", "Uint"] +__all__ = [ + "Type", + "Bool", + "Uint", +] import typing +class _Singleton(type): + """Metaclass to make the child, which should take zero initialization arguments, a singleton + object.""" + + def _get_singleton_instance(cls): + return cls._INSTANCE + + @classmethod + def __prepare__(mcs, name, bases): # pylint: disable=unused-argument + return {"__new__": mcs._get_singleton_instance} + + @staticmethod + def __new__(cls, name, bases, namespace): + out = super().__new__(cls, name, bases, namespace) + out._INSTANCE = object.__new__(out) # pylint: disable=invalid-name + return out + + class Type: """Root base class of all nodes in the type tree. The base case should never be instantiated directly. This must not be subclassed by users; subclasses form the internal data of the representation of - expressions, and it does not make sense to add more outside of Qiskit library code. - """ + expressions, and it does not make sense to add more outside of Qiskit library code.""" __slots__ = () - @property def kind(self): """Get the kind of this type. This is exactly equal to the Python type object that defines @@ -61,13 +81,13 @@ def __setstate__(self, state): @typing.final -class Bool(Type): +class Bool(Type, metaclass=_Singleton): """The Boolean type. This has exactly two values: ``True`` and ``False``.""" __slots__ = () def __repr__(self): - return f"Bool()" + return "Bool()" def __hash__(self): return hash(self.__class__) @@ -91,7 +111,7 @@ def __repr__(self): return f"Uint({self.width})" def __hash__(self): - return hash(self.__class__) + return hash((self.__class__, self.width)) def __eq__(self, other): return isinstance(other, Uint) and self.width == other.width diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 210b3a722134..e14c7aaf64a3 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -2751,14 +2751,13 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V Args: name_or_var: either a string of the variable name, or an existing instance of - a non-const-typed :class:`~.expr.Var` to re-use. Variables cannot shadow names - that are already in use within the circuit. + :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. If a const-typed expression is provided, it - will be automatically cast to its non-const counterpart. + 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`. @@ -2768,8 +2767,7 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V object will be returned. Raises: - CircuitError: if the variable cannot be created due to shadowing an existing variable - or a const variable was specified for ``name_or_var``. + 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:: @@ -2810,16 +2808,17 @@ def add_var(self, name_or_var: str | expr.Var, /, initial: typing.Any) -> expr.V # Validate the initializer first to catch cases where the variable to be declared is being # used in the initializer. circuit_scope = self._current_scope() - coerce_type = None - if isinstance(name_or_var, expr.Var): - if ( - name_or_var.type.kind is types.Uint - and isinstance(initial, int) - and not isinstance(initial, bool) - ): - # Convenience method to widen Python integer literals to the right width during - # the initial lift, if the type is already known via the variable. - coerce_type = name_or_var.type + # Convenience method to widen Python integer literals to the right width during the initial + # lift, if the type is already known via the variable. + if ( + isinstance(name_or_var, expr.Var) + and name_or_var.type.kind is types.Uint + and isinstance(initial, int) + and not isinstance(initial, bool) + ): + coerce_type = name_or_var.type + else: + coerce_type = None initial = _validate_expr(circuit_scope, expr.lift(initial, coerce_type)) if isinstance(name_or_var, str): var = expr.Var.new(name_or_var, initial.type) @@ -2932,10 +2931,8 @@ def add_input( # pylint: disable=missing-raises-doc 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): - if type_ is not None: - raise ValueError("cannot give an explicit type with an existing Var") - + 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 diff --git a/test/python/circuit/classical/test_expr_properties.py b/test/python/circuit/classical/test_expr_properties.py index 60a4b1f9080e..625db22cc12d 100644 --- a/test/python/circuit/classical/test_expr_properties.py +++ b/test/python/circuit/classical/test_expr_properties.py @@ -25,6 +25,14 @@ @ddt.ddt class TestExprProperties(QiskitTestCase): + def test_bool_type_is_singleton(self): + """The `Bool` type is meant (and used) as a Python singleton object for efficiency. It must + always be referentially equal to all other references to it.""" + self.assertIs(types.Bool(), types.Bool()) + self.assertIs(types.Bool(), copy.copy(types.Bool())) + self.assertIs(types.Bool(), copy.deepcopy(types.Bool())) + self.assertIs(types.Bool(), pickle.loads(pickle.dumps(types.Bool()))) + @ddt.data(types.Bool(), types.Uint(8)) def test_types_can_be_cloned(self, obj): """Test that various ways of cloning a `Type` object are valid and produce equal output.""" diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 4e286d5cffe5..99ee3fac3217 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -2223,8 +2223,7 @@ def _control_flow_expr_circuit(self): base.append(CustomCX(), [2, 4]) base.ry(a, 4) base.measure(4, 2) - # Use a const Uint RHS to make sure we QPY can serialize it. - with base.switch(expr.bit_and(base.cregs[0], expr.lift(2))) as case_: + with base.switch(expr.bit_and(base.cregs[0], 2)) as case_: with case_(0, 1): base.cz(3, 5) with case_(case_.DEFAULT):