Skip to content

Commit

Permalink
Add control flow support to QASM 3 exporter (#7299)
Browse files Browse the repository at this point in the history
* Add control-flow support to QASM 3 exporter

This teaches the OpenQASM 3 exporter, AST and printer to handle the new
control-flow instructions, and output them to valid OpenQASM 3.

There are nodes in the temporary AST to represent these, but significant
extra changes were required as well to support scoping and scoped
look-ups.  This commit does not add a proper symbol table, nor a proper
type system, which is how these look-ups should be handled, but instead
makes a relatively quick hack to patch in looking up qubits and clbits
from outer scopes.  This is because the functionality is being added
close to release of Terra 0.19, and we don't want to (nor have time to)
make overarching changes to the whole infrastructure right now.

The most notable change is that the 'context' management of the exporter
is now a nested stack; the outer is for "outer" contexts, like the main
program and subroutine/gate definitions (where we don't expect to share
scoped variables or qubits), while the inner stacks are for control-flow
scoping (where we do).  To _very quickly_ support these look-ups, we add
a `_Scope` struct that includes a look-up map of bits.

While refactoring the bit-look-ups to support the scoped access, it
became trivial to support loose bits, and to break the old assumption
that all bits are always in registers.  The exporter now supports loose
classical bits and qubits, and registers with overlapping bits.  For
classical bits, the export will fail with the default settings if there
are overlapping bits because there is little backend support for
classical aliases, but the `alias_classical_registers` option will swap
the handling to the alias form used for quantum registers, which can
handle overlapping bits.

* Use generic Enum, not IntEnum

In this case, the values weren't meant to be interchangeable with
integers anyway, it's just that the integer width is the only payload
information in the type.  In Python 3.6, the inheritance would break
when using IntEnum with multiple inheritance (due to how it already
inherits from int and Enum), so the solution is just to swap the type.

* Build ast.FloatType lookup from enum values

* Fix typos in tests

* Swap skip to expectedFailure

* Use loop parameter in for-loop tests

* Use capital letters for enum constants

* Clarify dangling-else comment

* Fix missing enum name changes

Co-authored-by: Kevin Krsulich <kevin.krsulich@ibm.com>
  • Loading branch information
jakelishman and kdk authored Dec 1, 2021
1 parent 6b6c683 commit 2c402dd
Show file tree
Hide file tree
Showing 7 changed files with 1,572 additions and 549 deletions.
4 changes: 2 additions & 2 deletions qiskit/circuit/library/standard_gates/x.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,8 @@ def _define_qasm3(self):
call = QuantumGateCall(
Identifier("U"),
[control, target],
parameters=[Constant.pi, Integer(0), Constant.pi],
modifiers=[QuantumGateModifier(QuantumGateModifierName.ctrl)],
parameters=[Constant.PI, Integer(0), Constant.PI],
modifiers=[QuantumGateModifier(QuantumGateModifierName.CTRL)],
)
return QuantumGateDefinition(
QuantumGateSignature(Identifier("cx"), [control, target]),
Expand Down
1 change: 1 addition & 0 deletions qiskit/qasm3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"""

from .exporter import Exporter
from .exceptions import QASM3Error, QASM3ExporterError


def dumps(circuit, **kwargs) -> str:
Expand Down
125 changes: 98 additions & 27 deletions qiskit/qasm3/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ def __init__(self):
pass


class ClassicalType(ASTNode):
"""Information about a classical type. This is just an abstract base for inheritance tests."""


class FloatType(ClassicalType, enum.Enum):
"""Allowed values for the width of floating-point types."""

HALF = 16
SINGLE = 32
DOUBLE = 64
QUAD = 128
OCT = 256


class BitArrayType(ClassicalType):
"""Type information for a sized number of classical bits."""

def __init__(self, size: int):
self.size = size


class Identifier(ASTNode):
"""
Identifier : FirstIdCharacter GeneralIdCharacter* ;
Expand Down Expand Up @@ -175,12 +196,23 @@ def __init__(self, identifier: Identifier, subscript: Union[Range, Expression]):
self.subscript = subscript


class IndexSet(ASTNode):
"""
A literal index set of values::
{ Expression (, Expression)* }
"""

def __init__(self, values: List[Expression]):
self.values = values


class Constant(Expression, enum.Enum):
"""A constant value defined by the QASM 3 spec."""

pi = enum.auto()
euler = enum.auto()
tau = enum.auto()
PI = enum.auto()
EULER = enum.auto()
TAU = enum.auto()


class QuantumMeasurement(ASTNode):
Expand Down Expand Up @@ -240,17 +272,13 @@ def __init__(self, expression: Expression):
self.expression = expression


class BitDeclaration(ASTNode):
"""
bitDeclaration
: ( 'creg' Identifier designator? | # NOT SUPPORTED
'bit' designator? Identifier ) equalsExpression?
"""
class ClassicalDeclaration(Statement):
"""Declaration of a classical type, optionally initialising it to a value."""

def __init__(self, identifier: Identifier, designator=None, equalsExpression=None):
def __init__(self, type_: ClassicalType, identifier: Identifier, initializer=None):
self.type = type_
self.identifier = identifier
self.designator = designator
self.equalsExpression = equalsExpression
self.initializer = initializer


class QuantumDeclaration(ASTNode):
Expand Down Expand Up @@ -279,10 +307,10 @@ def __init__(self, identifier: Identifier, concatenation: List[Identifier]):
class QuantumGateModifierName(enum.Enum):
"""The names of the allowed modifiers of quantum gates."""

ctrl = enum.auto()
negctrl = enum.auto()
inv = enum.auto()
pow = enum.auto()
CTRL = enum.auto()
NEGCTRL = enum.auto()
INV = enum.auto()
POW = enum.auto()


class QuantumGateModifier(ASTNode):
Expand Down Expand Up @@ -510,25 +538,68 @@ class BranchingStatement(Statement):
: 'if' LPAREN booleanExpression RPAREN programBlock ( 'else' programBlock )?
"""

def __init__(self, condition: BooleanExpression, true_body: ProgramBlock, false_body=None):
self.condition = condition
self.true_body = true_body
self.false_body = false_body


class ForLoopStatement(Statement):
"""
AST node for ``for`` loops.
::
ForLoop: "for" Identifier "in" SetDeclaration ProgramBlock
SetDeclaration:
| Identifier
| "{" Expression ("," Expression)* "}"
| "[" Range "]"
"""

def __init__(
self, booleanExpression: BooleanExpression, programTrue: ProgramBlock, programFalse=None
self,
parameter: Identifier,
indexset: Union[Identifier, IndexSet, Range],
body: ProgramBlock,
):
self.booleanExpression = booleanExpression
self.programTrue = programTrue
self.programFalse = programFalse
self.parameter = parameter
self.indexset = indexset
self.body = body


class WhileLoopStatement(Statement):
"""
AST node for ``while`` loops.
::
WhileLoop: "while" "(" Expression ")" ProgramBlock
"""

def __init__(self, condition: BooleanExpression, body: ProgramBlock):
self.condition = condition
self.body = body


class BreakStatement(Statement):
"""AST node for ``break`` statements. Has no associated information."""


class ContinueStatement(Statement):
"""AST node for ``continue`` statements. Has no associated information."""


class IOModifier(enum.Enum):
"""IO Modifier object"""

input = enum.auto()
output = enum.auto()
INPUT = enum.auto()
OUTPUT = enum.auto()


class IO(ASTNode):
"""UNDEFINED in the grammar yet"""
class IODeclaration(ClassicalDeclaration):
"""A declaration of an IO variable."""

def __init__(self, modifier: IOModifier, input_type, input_variable):
def __init__(self, modifier: IOModifier, type_: ClassicalType, identifier: Identifier):
super().__init__(type_, identifier)
self.modifier = modifier
self.type = input_type
self.variable = input_variable
23 changes: 23 additions & 0 deletions qiskit/qasm3/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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.

"""Exceptions that may be raised during processing OpenQASM 3."""

from qiskit.exceptions import QiskitError


class QASM3Error(QiskitError):
"""An error raised while working with OpenQASM 3 representations of circuits."""


class QASM3ExporterError(QASM3Error):
"""An error raised during running the OpenQASM 3 exporter."""
Loading

0 comments on commit 2c402dd

Please sign in to comment.