Skip to content

Commit

Permalink
Add initial representation of classical expressions
Browse files Browse the repository at this point in the history
This adds a method of representing a classical type system and
expression tree in Qiskit.  This initial version contains only simple
bitwise, logical and relational operations on Booleans and sized
unsigned integers.  This is largely in line with

    https://github.com/Qiskit/RFCs/blob/0dd13344/0010-simple-classical-representations.md

Some of the details surrounding construction and representation have
been changed slightly from that design, but the type system described is
the same.

In particular, this commit slightly modifies the representation of the
tree; instead of individual tree items for each unary and binary
operation, they are part of the `Unary` and `Binary` nodes with a
respective enumeration discriminator in the node.  This was done to make
visitors to the expression tree require less boilerplate and be easier
to maintain by exploiting the natural similarities between all these
operations of the same type.  One can still discriminate on the
particular operations by using a jump table based on the enumeration
values, if desired.

Secondly, a series of constructor functions was added that is separate
to the representation form.  This is for both efficiency and user
convenience.  The tree form is _purely_ a data format; it does no
type-checking on creation so that manipulations of the tree do not need
to pay runtime costs when the manipulators are the ones who will be
ensuring that the type system is fulfilled anyway.  The more user
friendly constructors _do_ resolve all this type information as part of
their construction.  They also infer typing information for Qiskit
scalar values, and insert casts as required.

Last, the tree has a `Cast` node added that is additional to the
description in the RFC.  This was done to make life easier for consumers
of the tree: they do not need to be aware of all the possible implicit
promotions that are required to make various unary and binary operations
valid.  This will reduce complexity of consumer code and reduce the
chance of bugs caused by different paths handling the casts differently.
  • Loading branch information
jakelishman committed Jun 24, 2023
1 parent fbd64d9 commit 69c902a
Show file tree
Hide file tree
Showing 12 changed files with 1,832 additions and 0 deletions.
6 changes: 6 additions & 0 deletions docs/apidocs/circuit_classical.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.. _qiskit-circuit-classical:

.. automodule:: qiskit.circuit.classical
:no-members:
:no-inherited-members:
:no-special-members:
1 change: 1 addition & 0 deletions docs/apidocs/terra.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Qiskit Terra API Reference

circuit
circuit_library
circuit_classical
compiler
execute
visualization
Expand Down
41 changes: 41 additions & 0 deletions qiskit/circuit/classical/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# 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.

"""
=======================================================
Classical expressions (:mod:`qiskit.circuit.classical`)
=======================================================
This module contains an exploratory representation of runtime operations on classical values during
circuit execution.
Currently, only simple expressions on bits and registers that result in a Boolean value are
supported, and these are only valid for use in the conditions of :meth:`.QuantumCircuit.if_test`
(:class:`.IfElseOp`) and :meth:`.QuantumCircuit.while_loop` (:class:`.WhileLoopOp`), and in the
target of :meth:`.QuantumCircuit.switch` (:class:`.SwitchCaseOp`).
.. note::
This is an exploratory module, and while we will commit to the standard Qiskit deprecation
policy within it, please be aware that the module will be deliberately limited in scope at the
start, and early versions may not evolve cleanly into the final version. It is possible that
various components of this module will be replaced (subject to deprecations) instead of improved
into a new form.
The type system and expression tree will be expanded over time, and it is possible that the
allowed types of some operations may need to change between versions of Qiskit as the classical
processing capabilities develop.
.. automodule:: qiskit.circuit.classical.expr
.. automodule:: qiskit.circuit.classical.types
"""

from . import types, expr
180 changes: 180 additions & 0 deletions qiskit/circuit/classical/expr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# 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.

"""
==================================================
Expressions (:mod:`qiskit.circuit.classical.expr`)
==================================================
The necessary components for building expressions are all exported from the
:mod:`~.qiskit.circuit.classical.expr` namespace within :mod:`qiskit.circuit.classical`, so you can
choose whether to use qualified access (for example :class:`.expr.Value`) or import the names you
need directly and call them without the prefix.
There are two pathways for constructing expressions. The classes that form :ref:`the
representation of the expression system <circuit-classical-expressions-expr-representation>`
have constructors that perform zero type checking; it is up to the caller to ensure that they
are building valid objects. For a more user-friendly interface to direct construction, there
are helper functions associated with most of the classes that do type validation and inference.
These are described below, in :ref:`circuit-classical-expressions-expr-construction`.
.. _circuit-classical-expressions-expr-representation:
Representation
==============
The expression system is based on tree representation. All nodes in the tree are final
(uninheritable) instances of the abstract base class:
.. autoclass:: Expr
These objects are mutable and should not be reused in a different location without a copy.
The entry point from general circuit objects to the expression system is by wrapping the object
in a :class:`Value` node and associating a :class:`~.types.Type` with it.
.. autoclass:: Value
The operations traditionally associated with pre-, post- or infix operators in programming are
represented by the :class:`Unary` and :class:`Binary` nodes as appropriate. These each take an
operation type code, which are exposed as enumerations inside each class as :class:`Unary.Op`
and :class:`Binary.Op` respectively.
.. autoclass:: Unary
:members: Op
:member-order: bysource
.. autoclass:: Binary
:members: Op
:member-order: bysource
When constructing expressions, one must ensure that the types are valid for the operation.
Attempts to construct expressions with invalid types will raise a regular Python ``TypeError``.
Expressions in this system are defined to act only on certain sets of types. However, values
may be cast to a suitable supertype in order to satisfy the typing requirements. In these
cases, a node in the expression tree is used to represent the promotion. In all cases where
operations note that they "implicitly cast" or "coerce" their arguments, the expression tree
must have this node representing the conversion.
.. autoclass:: Cast
.. _circuit-classical-expressions-expr-construction:
Construction
============
Constructing the tree representation directly is verbose and easy to make a mistake with the
typing. In many cases, much of the typing can be inferred, scalar values can automatically
be promoted to :class:`Value` instances, and any required promotions can be resolved into
suitable :class:`Cast` nodes.
The functions and methods described in this section are a more user-friendly way to build the
expression tree, while staying close to the internal representation. All these functions will
automatically lift valid Python scalar values into corresponding :class:`Value` objects, and
will resolve any required implicit casts on your behalf.
.. autofunction:: value
You can manually specify casts in cases where the cast is allowed in explicit form, but may be
losslses (such as the cast of a higher precision :class:`~.types.Uint` to a lower precision one).
.. autofunction:: cast
There are helper constructor functions for each of the unary operations.
.. autofunction:: bit_not
.. autofunction:: logic_not
Similarly, the binary operations and relations have helper functions defined.
.. autofunction:: bit_and
.. autofunction:: bit_or
.. autofunction:: logic_and
.. autofunction:: logic_or
.. autofunction:: equal
.. autofunction:: not_equal
.. autofunction:: less
.. autofunction:: less_equal
.. autofunction:: greater
.. autofunction:: greater_equal
Qiskit's legacy method for specifying equality conditions for use in conditionals is to use a
two-tuple of a :class:`.Clbit` or :class:`.ClassicalRegister` and an integer. This represents an
exact equality condition, and there are no ways to specify any other relations. The helper function
:func:`lift_legacy` converts this legacy format into the new expression syntax.
.. autofunction:: lift_legacy
Working with the expression tree
================================
A typical consumer of the expression tree wants to recursively walk through the tree, potentially
statefully, acting on each node differently depending on its type. This is naturally a
double-dispatch problem; the logic of 'what is to be done' is likely stateful and users should be
free to define their own operations, yet each node defines 'what is being acted on'. We enable this
double dispatch by providing a base visitor class for the expression tree.
.. autoclass:: ExprVisitor
:members:
:undoc-members:
Consumers of the expression tree should subclass the visitor, and override the ``visit_*`` methods
that they wish to handle. Any non-overridden methods will call :meth:`~ExprVisitor.visit_generic`,
which unless overridden will raise a ``RuntimeError`` to ensure that you are aware if new nodes
have been added to the expression tree that you are not yet handling.
"""

__all__ = [
"ExprVisitor",
"Expr",
"Value",
"Cast",
"Unary",
"Binary",
"value",
"cast",
"bit_not",
"logic_not",
"bit_and",
"bit_or",
"bit_xor",
"logic_and",
"logic_or",
"equal",
"not_equal",
"less",
"less_equal",
"greater",
"greater_equal",
"lift_legacy",
]

from .expr import ExprVisitor, Expr, Value, Cast, Unary, Binary
from .constructors import (
value,
cast,
bit_not,
logic_not,
bit_and,
bit_or,
bit_xor,
logic_and,
logic_or,
equal,
not_equal,
less,
less_equal,
greater,
greater_equal,
lift_legacy,
)
Loading

0 comments on commit 69c902a

Please sign in to comment.