diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py index eaac830edf65..c723c443fef2 100644 --- a/qiskit/qasm3/ast.py +++ b/qiskit/qasm3/ast.py @@ -15,12 +15,14 @@ """QASM3 AST Nodes""" import enum -from typing import Optional, List, Union, Iterable, Tuple +from typing import Optional, List, Union, Iterable, Tuple, Sequence class ASTNode: """Base abstract class for AST nodes""" + __slots__ = () + class Statement(ASTNode): """ @@ -35,6 +37,8 @@ class Statement(ASTNode): | quantumStatement """ + __slots__ = () + class Pragma(ASTNode): """ @@ -42,6 +46,8 @@ class Pragma(ASTNode): : '#pragma' LBRACE statement* RBRACE // match any valid openqasm statement """ + __slots__ = ("content",) + def __init__(self, content): self.content = content @@ -52,6 +58,8 @@ class CalibrationGrammarDeclaration(Statement): : 'defcalgrammar' calibrationGrammar SEMICOLON """ + __slots__ = ("name",) + def __init__(self, name): self.name = name @@ -62,9 +70,11 @@ class Program(ASTNode): : header (globalStatement | statement)* """ - def __init__(self, header, statements=None): + __slots__ = ("header", "statements") + + def __init__(self, header, statements=()): self.header = header - self.statements = statements or [] + self.statements = statements class Header(ASTNode): @@ -73,6 +83,8 @@ class Header(ASTNode): : version? include* """ + __slots__ = ("version", "includes") + def __init__(self, version, includes): self.version = version self.includes = includes @@ -84,6 +96,8 @@ class Include(ASTNode): : 'include' StringLiteral SEMICOLON """ + __slots__ = ("filename",) + def __init__(self, filename): self.filename = filename @@ -94,6 +108,8 @@ class Version(ASTNode): : 'OPENQASM'(Integer | RealNumber) SEMICOLON """ + __slots__ = ("version_number",) + def __init__(self, version_number): self.version_number = version_number @@ -108,10 +124,14 @@ class QuantumInstruction(ASTNode): | quantumBarrier """ + __slots__ = () + class ClassicalType(ASTNode): """Information about a classical type. This is just an abstract base for inheritance tests.""" + __slots__ = () + class FloatType(ClassicalType, enum.Enum): """Allowed values for the width of floating-point types.""" @@ -126,10 +146,14 @@ class FloatType(ClassicalType, enum.Enum): class BoolType(ClassicalType): """Type information for a Boolean.""" + __slots__ = () + class IntType(ClassicalType): """Type information for a signed integer.""" + __slots__ = ("size",) + def __init__(self, size: Optional[int] = None): self.size = size @@ -137,6 +161,8 @@ def __init__(self, size: Optional[int] = None): class UintType(ClassicalType): """Type information for an unsigned integer.""" + __slots__ = ("size",) + def __init__(self, size: Optional[int] = None): self.size = size @@ -144,19 +170,25 @@ def __init__(self, size: Optional[int] = None): class BitType(ClassicalType): """Type information for a single bit.""" + __slots__ = () + class BitArrayType(ClassicalType): """Type information for a sized number of classical bits.""" + __slots__ = ("size",) + def __init__(self, size: int): self.size = size class Expression(ASTNode): - pass + __slots__ = () class StringifyAndPray(Expression): + __slots__ = ("obj",) + # This is not a real AST node, yet is somehow very common. It's used when there are # `ParameterExpression` instances; instead of actually visiting the Sympy expression tree into # an OQ3 AST, we just convert it to a string, cross our fingers, and hope. @@ -165,6 +197,8 @@ def __init__(self, obj): class Range(Expression): + __slots__ = ("start", "step", "end") + def __init__( self, start: Optional[Expression] = None, @@ -177,6 +211,8 @@ def __init__( class Identifier(Expression): + __slots__ = ("string",) + def __init__(self, string): self.string = string @@ -184,6 +220,8 @@ def __init__(self, string): class SubscriptedIdentifier(Identifier): """An identifier with subscripted access.""" + __slots__ = ("subscript",) + def __init__(self, string: str, subscript: Union[Range, Expression]): super().__init__(string) self.subscript = subscript @@ -198,16 +236,22 @@ class Constant(Expression, enum.Enum): class IntegerLiteral(Expression): + __slots__ = ("value",) + def __init__(self, value): self.value = value class BooleanLiteral(Expression): + __slots__ = ("value",) + def __init__(self, value): self.value = value class BitstringLiteral(Expression): + __slots__ = ("value", "width") + def __init__(self, value, width): self.value = value self.width = width @@ -224,12 +268,16 @@ class DurationUnit(enum.Enum): class DurationLiteral(Expression): + __slots__ = ("value", "unit") + def __init__(self, value: float, unit: DurationUnit): self.value = value self.unit = unit class Unary(Expression): + __slots__ = ("op", "operand") + class Op(enum.Enum): LOGIC_NOT = "!" BIT_NOT = "~" @@ -240,6 +288,8 @@ def __init__(self, op: Op, operand: Expression): class Binary(Expression): + __slots__ = ("op", "left", "right") + class Op(enum.Enum): BIT_AND = "&" BIT_OR = "|" @@ -262,12 +312,16 @@ def __init__(self, op: Op, left: Expression, right: Expression): class Cast(Expression): + __slots__ = ("type", "operand") + def __init__(self, type: ClassicalType, operand: Expression): self.type = type self.operand = operand class Index(Expression): + __slots__ = ("target", "index") + def __init__(self, target: Expression, index: Expression): self.target = target self.index = index @@ -280,6 +334,8 @@ class IndexSet(ASTNode): { Expression (, Expression)* } """ + __slots__ = ("values",) + def __init__(self, values: List[Expression]): self.values = values @@ -290,6 +346,8 @@ class QuantumMeasurement(ASTNode): : 'measure' indexIdentifierList """ + __slots__ = ("identifierList",) + def __init__(self, identifierList: List[Identifier]): self.identifierList = identifierList @@ -301,6 +359,8 @@ class QuantumMeasurementAssignment(Statement): | indexIdentifier EQUALS quantumMeasurement # eg: bits = measure qubits; """ + __slots__ = ("identifier", "quantumMeasurement") + def __init__(self, identifier: Identifier, quantumMeasurement: QuantumMeasurement): self.identifier = identifier self.quantumMeasurement = quantumMeasurement @@ -312,6 +372,8 @@ class Designator(ASTNode): : LBRACKET expression RBRACKET """ + __slots__ = ("expression",) + def __init__(self, expression: Expression): self.expression = expression @@ -319,6 +381,8 @@ def __init__(self, expression: Expression): class ClassicalDeclaration(Statement): """Declaration of a classical type, optionally initializing it to a value.""" + __slots__ = ("type", "identifier", "initializer") + def __init__(self, type_: ClassicalType, identifier: Identifier, initializer=None): self.type = type_ self.identifier = identifier @@ -328,6 +392,8 @@ def __init__(self, type_: ClassicalType, identifier: Identifier, initializer=Non class AssignmentStatement(Statement): """Assignment of an expression to an l-value.""" + __slots__ = ("lvalue", "rvalue") + def __init__(self, lvalue: SubscriptedIdentifier, rvalue: Expression): self.lvalue = lvalue self.rvalue = rvalue @@ -340,6 +406,8 @@ class QuantumDeclaration(ASTNode): 'qubit' designator? Identifier """ + __slots__ = ("identifier", "designator") + def __init__(self, identifier: Identifier, designator=None): self.identifier = identifier self.designator = designator @@ -351,6 +419,8 @@ class AliasStatement(ASTNode): : 'let' Identifier EQUALS indexIdentifier SEMICOLON """ + __slots__ = ("identifier", "value") + def __init__(self, identifier: Identifier, value: Expression): self.identifier = identifier self.value = value @@ -368,6 +438,8 @@ class QuantumGateModifierName(enum.Enum): class QuantumGateModifier(ASTNode): """A modifier of a gate. For example, in ``ctrl @ x $0``, the ``ctrl @`` is the modifier.""" + __slots__ = ("modifier", "argument") + def __init__(self, modifier: QuantumGateModifierName, argument: Optional[Expression] = None): self.modifier = modifier self.argument = argument @@ -379,17 +451,19 @@ class QuantumGateCall(QuantumInstruction): : quantumGateModifier* quantumGateName ( LPAREN expressionList? RPAREN )? indexIdentifierList """ + __slots__ = ("quantumGateName", "indexIdentifierList", "parameters", "modifiers") + def __init__( self, quantumGateName: Identifier, indexIdentifierList: List[Identifier], - parameters: List[Expression] = None, - modifiers: Optional[List[QuantumGateModifier]] = None, + parameters: Sequence[Expression] = (), + modifiers: Sequence[QuantumGateModifier] = (), ): self.quantumGateName = quantumGateName self.indexIdentifierList = indexIdentifierList - self.parameters = parameters or [] - self.modifiers = modifiers or [] + self.parameters = parameters + self.modifiers = modifiers class QuantumBarrier(QuantumInstruction): @@ -398,6 +472,8 @@ class QuantumBarrier(QuantumInstruction): : 'barrier' indexIdentifierList """ + __slots__ = ("indexIdentifierList",) + def __init__(self, indexIdentifierList: List[Identifier]): self.indexIdentifierList = indexIdentifierList @@ -405,6 +481,8 @@ def __init__(self, indexIdentifierList: List[Identifier]): class QuantumReset(QuantumInstruction): """A built-in ``reset q0;`` statement.""" + __slots__ = ("identifier",) + def __init__(self, identifier: Identifier): self.identifier = identifier @@ -412,6 +490,8 @@ def __init__(self, identifier: Identifier): class QuantumDelay(QuantumInstruction): """A built-in ``delay[duration] q0;`` statement.""" + __slots__ = ("duration", "qubits") + def __init__(self, duration: Expression, qubits: List[Identifier]): self.duration = duration self.qubits = qubits @@ -424,6 +504,8 @@ class ProgramBlock(ASTNode): | LBRACE(statement | controlDirective) * RBRACE """ + __slots__ = ("statements",) + def __init__(self, statements: List[Statement]): self.statements = statements @@ -434,6 +516,8 @@ class ReturnStatement(ASTNode): # TODO probably should be a subclass of Control : 'return' ( expression | quantumMeasurement )? SEMICOLON; """ + __slots__ = ("expression",) + def __init__(self, expression=None): self.expression = expression @@ -444,7 +528,7 @@ class QuantumBlock(ProgramBlock): : LBRACE ( quantumStatement | quantumLoop )* RBRACE """ - pass + __slots__ = () class SubroutineBlock(ProgramBlock): @@ -453,7 +537,7 @@ class SubroutineBlock(ProgramBlock): : LBRACE statement* returnStatement? RBRACE """ - pass + __slots__ = () class QuantumGateDefinition(Statement): @@ -462,6 +546,8 @@ class QuantumGateDefinition(Statement): : 'gate' quantumGateSignature quantumBlock """ + __slots__ = ("name", "params", "qubits", "body") + def __init__( self, name: Identifier, @@ -482,14 +568,16 @@ class SubroutineDefinition(Statement): returnSignature? subroutineBlock """ + __slots__ = ("identifier", "arguments", "subroutineBlock") + def __init__( self, identifier: Identifier, subroutineBlock: SubroutineBlock, - arguments=None, # [ClassicalArgument] + arguments=(), # [ClassicalArgument] ): self.identifier = identifier - self.arguments = arguments or [] + self.arguments = arguments self.subroutineBlock = subroutineBlock @@ -499,7 +587,7 @@ class CalibrationArgument(ASTNode): : classicalArgumentList | expressionList """ - pass + __slots__ = () class CalibrationDefinition(Statement): @@ -511,15 +599,17 @@ class CalibrationDefinition(Statement): ; """ + __slots__ = ("name", "identifierList", "calibrationArgumentList") + def __init__( self, name: Identifier, identifierList: List[Identifier], - calibrationArgumentList: Optional[List[CalibrationArgument]] = None, + calibrationArgumentList: Sequence[CalibrationArgument] = (), ): self.name = name self.identifierList = identifierList - self.calibrationArgumentList = calibrationArgumentList or [] + self.calibrationArgumentList = calibrationArgumentList class BranchingStatement(Statement): @@ -528,6 +618,8 @@ class BranchingStatement(Statement): : 'if' LPAREN booleanExpression RPAREN programBlock ( 'else' programBlock )? """ + __slots__ = ("condition", "true_body", "false_body") + def __init__(self, condition: Expression, true_body: ProgramBlock, false_body=None): self.condition = condition self.true_body = true_body @@ -547,6 +639,8 @@ class ForLoopStatement(Statement): | "[" Range "]" """ + __slots__ = ("indexset", "parameter", "body") + def __init__( self, indexset: Union[Identifier, IndexSet, Range], @@ -567,6 +661,8 @@ class WhileLoopStatement(Statement): WhileLoop: "while" "(" Expression ")" ProgramBlock """ + __slots__ = ("condition", "body") + def __init__(self, condition: Expression, body: ProgramBlock): self.condition = condition self.body = body @@ -575,10 +671,14 @@ def __init__(self, condition: Expression, body: ProgramBlock): class BreakStatement(Statement): """AST node for ``break`` statements. Has no associated information.""" + __slots__ = () + class ContinueStatement(Statement): """AST node for ``continue`` statements. Has no associated information.""" + __slots__ = () + class IOModifier(enum.Enum): """IO Modifier object""" @@ -590,6 +690,8 @@ class IOModifier(enum.Enum): class IODeclaration(ClassicalDeclaration): """A declaration of an IO variable.""" + __slots__ = ("modifier",) + def __init__(self, modifier: IOModifier, type_: ClassicalType, identifier: Identifier): super().__init__(type_, identifier) self.modifier = modifier @@ -598,6 +700,8 @@ def __init__(self, modifier: IOModifier, type_: ClassicalType, identifier: Ident class DefaultCase(Expression): """An object representing the `default` special label in switch statements.""" + __slots__ = () + class SwitchStatementPreview(Statement): """AST node for the proposed 'switch-case' extension to OpenQASM 3, before the syntax was @@ -605,6 +709,8 @@ class SwitchStatementPreview(Statement): The stabilized form of the syntax instead uses :class:`.SwitchStatement`.""" + __slots__ = ("target", "cases") + def __init__( self, target: Expression, cases: Iterable[Tuple[Iterable[Expression], ProgramBlock]] ): @@ -619,6 +725,8 @@ class SwitchStatement(Statement): cannot be joined with other cases (even though that's meaningless, the V1 syntax permitted it). """ + __slots__ = ("target", "cases", "default") + def __init__( self, target: Expression, diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index c76e296d6bab..816c0809c72b 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -597,12 +597,10 @@ def new_context(self, body: QuantumCircuit): self.scope = old_scope self.symbols = old_symbols - def _lookup_variable(self, variable) -> ast.Identifier: - """Lookup a Qiskit object within the current context, and return the name that should be + def _lookup_bit(self, bit) -> ast.Identifier: + """Lookup a Qiskit bit within the current context, and return the name that should be used to represent it in OpenQASM 3 programmes.""" - if isinstance(variable, Bit): - variable = self.scope.bit_map[variable] - return self.symbols.get_variable(variable) + return self.symbols.get_variable(self.scope.bit_map[bit]) def build_program(self): """Builds a Program""" @@ -909,7 +907,7 @@ def build_aliases(self, registers: Iterable[Register]) -> List[ast.AliasStatemen out = [] for register in registers: name = self.symbols.register_variable(register.name, register, allow_rename=True) - elements = [self._lookup_variable(bit) for bit in register] + elements = [self._lookup_bit(bit) for bit in register] for i, bit in enumerate(register): # This might shadow previous definitions, but that's not a problem. self.symbols.set_object_ident( @@ -956,18 +954,17 @@ def build_current_scope(self) -> List[ast.Statement]: if isinstance(instruction.operation, Gate): nodes = [self.build_gate_call(instruction)] elif isinstance(instruction.operation, Barrier): - operands = [self._lookup_variable(operand) for operand in instruction.qubits] + operands = [self._lookup_bit(operand) for operand in instruction.qubits] nodes = [ast.QuantumBarrier(operands)] elif isinstance(instruction.operation, Measure): measurement = ast.QuantumMeasurement( - [self._lookup_variable(operand) for operand in instruction.qubits] + [self._lookup_bit(operand) for operand in instruction.qubits] ) - qubit = self._lookup_variable(instruction.clbits[0]) + qubit = self._lookup_bit(instruction.clbits[0]) nodes = [ast.QuantumMeasurementAssignment(qubit, measurement)] elif isinstance(instruction.operation, Reset): nodes = [ - ast.QuantumReset(self._lookup_variable(operand)) - for operand in instruction.qubits + ast.QuantumReset(self._lookup_bit(operand)) for operand in instruction.qubits ] elif isinstance(instruction.operation, Delay): nodes = [self.build_delay(instruction)] @@ -1101,9 +1098,14 @@ def build_for_loop(self, instruction: CircuitInstruction) -> ast.ForLoopStatemen body_ast = ast.ProgramBlock(self.build_current_scope()) return ast.ForLoopStatement(indexset_ast, loop_parameter_ast, body_ast) + def _lookup_variable_for_expression(self, var): + if isinstance(var, Bit): + return self._lookup_bit(var) + return self.symbols.get_variable(var) + def build_expression(self, node: expr.Expr) -> ast.Expression: """Build an expression.""" - return node.accept(_ExprBuilder(self._lookup_variable)) + return node.accept(_ExprBuilder(self._lookup_variable_for_expression)) def build_delay(self, instruction: CircuitInstruction) -> ast.QuantumDelay: """Build a built-in delay statement.""" @@ -1123,9 +1125,7 @@ def build_delay(self, instruction: CircuitInstruction) -> ast.QuantumDelay: "dt": ast.DurationUnit.SAMPLE, } duration = ast.DurationLiteral(duration_value, unit_map[unit]) - return ast.QuantumDelay( - duration, [self._lookup_variable(qubit) for qubit in instruction.qubits] - ) + return ast.QuantumDelay(duration, [self._lookup_bit(qubit) for qubit in instruction.qubits]) def build_integer(self, value) -> ast.IntegerLiteral: """Build an integer literal, raising a :obj:`.QASM3ExporterError` if the input is not @@ -1145,9 +1145,11 @@ def _rebind_scoped_parameters(self, expression): # missing, pending a new system in Terra to replace it (2022-03-07). if not isinstance(expression, ParameterExpression): return expression + if isinstance(expression, Parameter): + return self.symbols.get_variable(expression).string return expression.subs( { - param: Parameter(self._lookup_variable(param).string) + param: Parameter(self.symbols.get_variable(param).string, uuid=param.uuid) for param in expression.parameters } ) @@ -1160,18 +1162,14 @@ def build_gate_call(self, instruction: CircuitInstruction): ident = self.symbols.get_gate(instruction.operation) if ident is None: ident = self.define_gate(instruction.operation) - qubits = [self._lookup_variable(qubit) for qubit in instruction.qubits] - if self.disable_constants: - parameters = [ - ast.StringifyAndPray(self._rebind_scoped_parameters(param)) - for param in instruction.operation.params - ] - else: - parameters = [ - ast.StringifyAndPray(pi_check(self._rebind_scoped_parameters(param), output="qasm")) - for param in instruction.operation.params - ] - + qubits = [self._lookup_bit(qubit) for qubit in instruction.qubits] + parameters = [ + ast.StringifyAndPray(self._rebind_scoped_parameters(param)) + for param in instruction.operation.params + ] + if not self.disable_constants: + for parameter in parameters: + parameter.obj = pi_check(parameter.obj, output="qasm") return ast.QuantumGateCall(ident, qubits, parameters=parameters)