Skip to content

Commit

Permalink
Add variable-handling methods to QuantumCircuit
Browse files Browse the repository at this point in the history
This adds all the new `QuantumCircuit` methods discussed in the
variable-declaration RFC[^1], and threads the support for them through
the methods that are called in turn, such as `QuantumCircuit.append`.
It does yet not add support to methods such as `copy` or `compose`,
which will be done in a follow-up.

The APIs discussed in the RFC necessitated making `Var` nodes hashable.
This is done in this commit, as it is logically connected.  These nodes
now have enforced immutability, which is technically a minor breaking
change, but in practice required for the properties of such expressions
to be tracked correctly through circuits.

A helper attribute `Var.standalone` is added to unify the handling of
whether a variable is an old-style existing-memory wrapper, or a new
"proper" variable with its own memory.

[^1]: Qiskit/RFCs#50
  • Loading branch information
jakelishman committed Nov 24, 2023
1 parent 3d2f8dd commit 424ec66
Show file tree
Hide file tree
Showing 8 changed files with 939 additions and 19 deletions.
62 changes: 49 additions & 13 deletions qiskit/circuit/classical/expr/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,38 +115,57 @@ class Var(Expr):
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`."""
:meth:`.QuantumCircuit.add_var`.
Variables are immutable after construction, so they can be used as dictionary keys."""

__slots__ = ("var", "name")

var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID
"""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."""

def __init__(
self,
var: qiskit.circuit.Clbit | qiskit.circuit.ClassicalRegister | uuid.UUID,
type: types.Type,
*,
name: str | None = None,
):
self.type = type
self.var = var
"""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."""
self.name = name
"""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."""
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 some pre-existing circuit object."""
return isinstance(self.var, uuid.UUID)

def accept(self, visitor, /):
return visitor.visit_var(self)

def __setattr__(self, key, value):
if hasattr(self, key):
raise AttributeError(f"'Var' object attribute '{key}' is read-only")
raise AttributeError(f"'Var' object has no attribute '{key}'")

def __hash__(self):
return hash((self.type, self.var, self.name))

def __eq__(self, other):
return (
isinstance(other, Var)
Expand All @@ -160,6 +179,23 @@ def __repr__(self):
return f"Var({self.var}, {self.type})"
return f"Var({self.var}, {self.type}, name='{self.name}')"

def __getstate__(self):
return (self.var, self.type, self.name)

def __setstate__(self, state):
var, type, name = state
super().__setattr__("type", type)
super().__setattr__("var", var)
super().__setattr__("name", name)

def __copy__(self):
# I am immutable...
return self

def __deepcopy__(self, memo):
# ... as are all my consituent parts.
return self


@typing.final
class Value(Expr):
Expand Down
6 changes: 6 additions & 0 deletions qiskit/circuit/classical/types/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class Bool(Type, metaclass=_Singleton):
def __repr__(self):
return "Bool()"

def __hash__(self):
return hash(self.__class__)

def __eq__(self, other):
return isinstance(other, Bool)

Expand All @@ -107,5 +110,8 @@ def __init__(self, width: int):
def __repr__(self):
return f"Uint({self.width})"

def __hash__(self):
return hash((self.__class__, self.width))

def __eq__(self, other):
return isinstance(other, Uint) and self.width == other.width
Loading

0 comments on commit 424ec66

Please sign in to comment.