Skip to content

Commit

Permalink
Output arrays in QASM 3 alias concatenations (Qiskit#7221)
Browse files Browse the repository at this point in the history
* Output arrays in QASM 3 alias concatenations

The concatenation `||` operator in OpenQASM 3 is currently supposed to
operate only on arrays, but the exporter had been using it on single
qubits.  This changes the logic so that all the elements of a catenation
are arrays, and attempts to make each run as long as possible.

A new node to represent a `Range` was necessary to achieve this.  This
also meant reorganising the split between `IndexIdentifier`,
`IndexIdentifier2` and the builder-internal `indexIdentifierList`.
These names were originally taken from the OpenQASM 3 reference ANTLR
grammar, but the grammar has changed somewhat since then, and there are
also more descriptive names available.

* Fix typo

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
  • Loading branch information
jakelishman and 1ucian0 authored Nov 10, 2021
1 parent 49110f9 commit aac9917
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 88 deletions.
64 changes: 34 additions & 30 deletions qiskit/qasm3/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"""QASM3 AST Nodes"""

import enum
from typing import Optional, List
from typing import Optional, List, Union


class ASTNode:
Expand Down Expand Up @@ -132,17 +132,6 @@ def __init__(self, identifier: Identifier):
self.identifier = identifier


class IndexIdentifier(Identifier):
"""
indexIdentifier
: Identifier rangeDefinition
| Identifier ( LBRACKET expressionList RBRACKET )?
| indexIdentifier '||' indexIdentifier
"""

pass


class Expression(ASTNode):
"""
expression
Expand All @@ -158,25 +147,40 @@ def __init__(self, something):
self.something = something


class Constant(Expression, enum.Enum):
"""A constant value defined by the QASM 3 spec."""
class Range(ASTNode):
"""
A range expression::
pi = enum.auto()
euler = enum.auto()
tau = enum.auto()
<start>? (: <step>)? : <end>?
"""

def __init__(
self,
start: Optional[Expression] = None,
end: Optional[Expression] = None,
step: Optional[Expression] = None,
):
self.start = start
self.step = step
self.end = end


class IndexIdentifier2(IndexIdentifier):
class SubscriptedIdentifier(Identifier):
"""
indexIdentifier
: Identifier rangeDefinition
| Identifier ( LBRACKET expressionList RBRACKET )? <--
| indexIdentifier '||' indexIdentifier
An identifier with subscripted access.
"""

def __init__(self, identifier: Identifier, expressionList: Optional[List[Expression]] = None):
def __init__(self, identifier: Identifier, subscript: Union[Range, Expression]):
self.identifier = identifier
self.expressionList = expressionList
self.subscript = subscript


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

pi = enum.auto()
euler = enum.auto()
tau = enum.auto()


class QuantumMeasurement(ASTNode):
Expand All @@ -185,8 +189,8 @@ class QuantumMeasurement(ASTNode):
: 'measure' indexIdentifierList
"""

def __init__(self, indexIdentifierList: List[Identifier]):
self.indexIdentifierList = indexIdentifierList
def __init__(self, identifierList: List[Identifier]):
self.identifierList = identifierList


class QuantumMeasurementAssignment(Statement):
Expand All @@ -196,8 +200,8 @@ class QuantumMeasurementAssignment(Statement):
| indexIdentifier EQUALS quantumMeasurement # eg: bits = measure qubits;
"""

def __init__(self, indexIdentifier: IndexIdentifier2, quantumMeasurement: QuantumMeasurement):
self.indexIdentifier = indexIdentifier
def __init__(self, identifier: Identifier, quantumMeasurement: QuantumMeasurement):
self.identifier = identifier
self.quantumMeasurement = quantumMeasurement


Expand Down Expand Up @@ -267,9 +271,9 @@ class AliasStatement(ASTNode):
: 'let' Identifier EQUALS indexIdentifier SEMICOLON
"""

def __init__(self, identifier: Identifier, qubits: List[IndexIdentifier2]):
def __init__(self, identifier: Identifier, concatenation: List[Identifier]):
self.identifier = identifier
self.qubits = qubits
self.concatenation = concatenation


class QuantumGateModifierName(enum.Enum):
Expand Down
74 changes: 43 additions & 31 deletions qiskit/qasm3/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
Include,
Header,
Identifier,
IndexIdentifier2,
SubscriptedIdentifier,
Range,
QuantumBlock,
QuantumBarrier,
Designator,
Expand Down Expand Up @@ -448,15 +449,33 @@ def build_quantumdeclarations(self):
)
# aliases
for qreg in self.circuit_ctx[-1].qregs:
qubits = []
elements = []
# Greedily consolidate runs of qubits into ranges. We don't bother trying to handle
# steps; there's no need in generated code. Even single qubits are referenced as ranges
# because the concatenation in an alias statement can only concatenate arraylike values.
start_index, prev_index = None, None
for qubit in qreg:
qubits.append(
IndexIdentifier2(
cur_index = self.circuit_ctx[-1].find_bit(qubit).index
if start_index is None:
start_index = cur_index
elif cur_index != prev_index + 1:
elements.append(
SubscriptedIdentifier(
Identifier(self.base_register_name),
Range(start=Integer(start_index), end=Integer(prev_index)),
)
)
start_index = prev_index = cur_index
prev_index = cur_index
# After the loop, if there were any bits at all, there's always one unemitted range.
if len(qreg) != 0:
elements.append(
SubscriptedIdentifier(
Identifier(self.base_register_name),
[Integer(self.circuit_ctx[-1].find_bit(qubit).index)],
Range(start=Integer(start_index), end=Integer(prev_index)),
)
)
ret.append(AliasStatement(Identifier(qreg.name), qubits))
ret.append(AliasStatement(Identifier(qreg.name), elements))
return ret

def build_quantuminstructions(self, instructions):
Expand All @@ -475,17 +494,17 @@ def build_quantuminstructions(self, instructions):
else:
ret.append(self.build_quantumgatecall(instruction))
elif isinstance(instruction[0], Barrier):
indexIdentifierList = self.build_indexIdentifierlist(instruction[1])
ret.append(QuantumBarrier(indexIdentifierList))
operands = [self.build_single_bit_reference(operand) for operand in instruction[1]]
ret.append(QuantumBarrier(operands))
elif isinstance(instruction[0], Measure):
quantumMeasurement = QuantumMeasurement(
self.build_indexIdentifierlist(instruction[1])
[self.build_single_bit_reference(operand) for operand in instruction[1]]
)
indexIdentifierList = self.build_indexidentifier(instruction[2][0])
ret.append(QuantumMeasurementAssignment(indexIdentifierList, quantumMeasurement))
qubit = self.build_single_bit_reference(instruction[2][0])
ret.append(QuantumMeasurementAssignment(qubit, quantumMeasurement))
elif isinstance(instruction[0], Reset):
for identifier in instruction[1]:
ret.append(QuantumReset(self.build_indexidentifier(identifier)))
for operand in instruction[1]:
ret.append(QuantumReset(self.build_single_bit_reference(operand)))
else:
ret.append(self.build_subroutinecall(instruction))
return ret
Expand All @@ -497,7 +516,7 @@ def build_programblock(self, instructions):
def build_eqcondition(self, condition):
"""Classical Conditional condition from a instruction.condition"""
if isinstance(condition[0], Clbit):
condition_on = self.build_indexidentifier(condition[0])
condition_on = self.build_single_bit_reference(condition[0])
else:
condition_on = Identifier(condition[0].name)
return ComparisonExpression(condition_on, EqualsOperator(), Integer(int(condition[1])))
Expand All @@ -519,49 +538,42 @@ def build_quantumArgumentList(self, qregs: [QuantumRegister], circuit=None):
)
return quantumArgumentList

def build_indexIdentifierlist(self, bitlist: [Bit]):
"""Builds a indexIdentifierList"""
indexIdentifierList = []
for bit in bitlist:
indexIdentifierList.append(self.build_indexidentifier(bit))
return indexIdentifierList

def build_quantumgatecall(self, instruction):
"""Builds a QuantumGateCall"""
if isinstance(instruction[0], UGate):
quantumGateName = Identifier("U")
else:
quantumGateName = Identifier(self.global_namespace[instruction[0]])
indexIdentifierList = self.build_indexIdentifierlist(instruction[1])
qubits = [self.build_single_bit_reference(qubit) for qubit in instruction[1]]
if self.disable_constants:
parameters = [Expression(param) for param in instruction[0].params]
else:
parameters = [
Expression(pi_check(param, output="qasm")) for param in instruction[0].params
]

return QuantumGateCall(quantumGateName, indexIdentifierList, parameters=parameters)
return QuantumGateCall(quantumGateName, qubits, parameters=parameters)

def build_subroutinecall(self, instruction):
"""Builds a SubroutineCall"""
identifier = Identifier(self.global_namespace[instruction[0]])
expressionList = [Expression(param) for param in instruction[0].params]
indexIdentifierList = self.build_indexIdentifierlist(instruction[1])

# TODO: qubits should go inside the brackets of subroutine calls, but neither Terra nor the
# AST here really support the calls, so there's no sensible way of writing it yet.
indexIdentifierList = [self.build_single_bit_reference(bit) for bit in instruction[1]]
return SubroutineCall(identifier, indexIdentifierList, expressionList)

def build_indexidentifier(self, bit: Bit):
"""Build a IndexIdentifier2"""
reg, idx = self.find_bit(bit)
def build_single_bit_reference(self, bit: Bit) -> Identifier:
"""Get an identifier node that refers to one particular bit."""
if self._physical_qubit and isinstance(bit, Qubit):
return PhysicalQubitIdentifier(
Identifier(str(self.circuit_ctx[-1].find_bit(bit).index))
)
reg, idx = self.find_bit(bit)
if self._flat_reg:
bit_name = f"{reg.name}_{idx}"
return IndexIdentifier2(Identifier(bit_name))
else:
return IndexIdentifier2(Identifier(reg.name), [Integer(idx)])
return Identifier(bit_name)
return SubscriptedIdentifier(Identifier(reg.name), Integer(idx))

def find_bit(self, bit):
"""Returns the register and the index in that register for a particular bit"""
Expand Down
26 changes: 19 additions & 7 deletions qiskit/qasm3/printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
IOModifier,
Identifier,
Include,
IndexIdentifier2,
Integer,
LtOperator,
PhysicalQubitIdentifier,
Expand All @@ -51,9 +50,11 @@
QuantumMeasurement,
QuantumMeasurementAssignment,
QuantumReset,
Range,
ReturnStatement,
SubroutineCall,
SubroutineDefinition,
SubscriptedIdentifier,
Version,
)

Expand Down Expand Up @@ -177,18 +178,29 @@ def _visit_Expression(self, node: Expression) -> None:
def _visit_Constant(self, node: Constant) -> None:
self.stream.write(self._CONSTANT_LOOKUP[node])

def _visit_IndexIdentifier2(self, node: IndexIdentifier2) -> None:
def _visit_SubscriptedIdentifier(self, node: SubscriptedIdentifier) -> None:
self.visit(node.identifier)
if node.expressionList:
self._visit_sequence(node.expressionList, start="[", end="]", separator=", ")
self.stream.write("[")
self.visit(node.subscript)
self.stream.write("]")

def _visit_Range(self, node: Range) -> None:
if node.start is not None:
self.visit(node.start)
self.stream.write(":")
if node.step is not None:
self.visit(node.step)
self.stream.write(":")
if node.end is not None:
self.visit(node.end)

def _visit_QuantumMeasurement(self, node: QuantumMeasurement) -> None:
self.stream.write("measure ")
self._visit_sequence(node.indexIdentifierList, separator=", ")
self._visit_sequence(node.identifierList, separator=", ")

def _visit_QuantumMeasurementAssignment(self, node: QuantumMeasurementAssignment) -> None:
self._start_line()
self.visit(node.indexIdentifier)
self.visit(node.identifier)
self.stream.write(" = ")
self.visit(node.quantumMeasurement)
self._end_statement()
Expand Down Expand Up @@ -231,7 +243,7 @@ def _visit_AliasStatement(self, node: AliasStatement) -> None:
self.stream.write("let ")
self.visit(node.identifier)
self.stream.write(" = ")
self._visit_sequence(node.qubits, separator=" || ")
self._visit_sequence(node.concatenation, separator=" || ")
self._end_statement()

def _visit_QuantumGateModifier(self, node: QuantumGateModifier) -> None:
Expand Down
Loading

0 comments on commit aac9917

Please sign in to comment.