Skip to content

Commit

Permalink
Revert stuff I didn't need to touch.
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinhartman committed Feb 26, 2025
1 parent ca2785d commit e902009
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 191 deletions.
201 changes: 66 additions & 135 deletions qiskit/circuit/classical/expr/constructors.py

Large diffs are not rendered by default.

12 changes: 1 addition & 11 deletions qiskit/circuit/classical/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
17 changes: 1 addition & 16 deletions qiskit/circuit/classical/types/ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -227,14 +215,11 @@ def cast_kind(from_: Type, to_: Type, /) -> CastKind:
<CastKind.EQUAL: 1>
>>> types.cast_kind(types.Uint(8), types.Bool())
<CastKind.IMPLICIT: 2>
>>> types.cast_kind(types.Uint(8, const=True), types.Uint(8))
<CastKind.IMPLICIT: 2>
>>> types.cast_kind(types.Bool(), types.Uint(8))
<CastKind.LOSSLESS: 3>
>>> types.cast_kind(types.Uint(16), types.Uint(8))
<CastKind.DANGEROUS: 4>
"""
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_)
34 changes: 27 additions & 7 deletions qiskit/circuit/classical/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)
Expand All @@ -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
37 changes: 17 additions & 20 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand All @@ -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::
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions test/python/circuit/classical/test_expr_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
3 changes: 1 addition & 2 deletions test/python/compiler/test_transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down

0 comments on commit e902009

Please sign in to comment.