From 2194f5534d0825e69b6eb70fec6e653a3e753622 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Tue, 18 Jul 2023 22:09:58 +0100 Subject: [PATCH] Refactor output of OpenQASM 3 exporter to use fewer aliases (#10249) * Refactor output of OpenQASM 3 exporter to use fewer aliases This removes spurious `_loose_bit` "registers" from the OpenQASM 3 output, and instead emits loose bits with individual `bit` and `qubit` declarations. Non-overlapping registers are emitted using regular `bit[n]` and `qubit[n]` definitions when possible, and we only resort to aliasing if we must to describe the structure. This avoids introducing structure to the definitions that does not exist in the original program, making round-trips and interactions with other OQ3 consumers more straightforwards. It's better not to use advanced features that don't map to hardware particularly well when it's not necessary. On the technical side, all bits are now properly tracked in the symbol table. Previously, there was a lot of code duplication, internal state tracking and magic inferences that attempted to "guess" how a qubit/clbit should be referred to. Instead, we just properly add them as variables to the symbol table, which also drastically reduces the number of objects that effectively reserve names that the user may not use. * Remove unnecessary empty init * Fix typo Co-authored-by: Ian Hincks * Add explicit test of old option --------- Co-authored-by: Ian Hincks --- qiskit/qasm3/ast.py | 11 +- qiskit/qasm3/exporter.py | 357 +++++++----------- qiskit/qasm3/printer.py | 8 +- ...qasm3-alias-refactor-3389bfce3e29e4cf.yaml | 15 + test/python/qasm3/test_export.py | 288 +++++++------- 5 files changed, 315 insertions(+), 364 deletions(-) create mode 100644 releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml diff --git a/qiskit/qasm3/ast.py b/qiskit/qasm3/ast.py index 83313668a286..c4746229be95 100644 --- a/qiskit/qasm3/ast.py +++ b/qiskit/qasm3/ast.py @@ -110,9 +110,6 @@ class QuantumInstruction(ASTNode): | quantumBarrier """ - def __init__(self): - pass - class ClassicalType(ASTNode): """Information about a classical type. This is just an abstract base for inheritance tests.""" @@ -135,6 +132,10 @@ def __init__(self, size: Optional[int] = None): self.size = size +class BitType(ClassicalType): + """Type information for a single bit.""" + + class BitArrayType(ClassicalType): """Type information for a sized number of classical bits.""" @@ -332,9 +333,9 @@ class AliasStatement(ASTNode): : 'let' Identifier EQUALS indexIdentifier SEMICOLON """ - def __init__(self, identifier: Identifier, concatenation: List[Identifier]): + def __init__(self, identifier: Identifier, value: Expression): self.identifier = identifier - self.concatenation = concatenation + self.value = value class QuantumGateModifierName(enum.Enum): diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index 2bb1919933e7..018d8ba5c327 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -30,7 +30,6 @@ Parameter, ParameterExpression, QuantumCircuit, - QuantumRegister, Qubit, Reset, Delay, @@ -132,7 +131,8 @@ def __init__( includes: Sequence[str] = ("stdgates.inc",), basis_gates: Sequence[str] = ("U",), disable_constants: bool = False, - alias_classical_registers: bool = False, + alias_classical_registers: bool = None, + allow_aliasing: bool = None, indent: str = " ", experimental: ExperimentalFeatures = ExperimentalFeatures(0), ): @@ -146,15 +146,20 @@ def __init__( parameter values. If ``False`` (the default), then values close to multiples of QASM 3 constants (``pi``, ``euler``, and ``tau``) will be emitted in terms of those constants instead, potentially improving accuracy in the output. - alias_classical_registers: If ``True``, then classical bit and classical register - declarations will look similar to quantum declarations, where the whole set of bits - will be declared in a flat array, and the registers will just be aliases to - collections of these bits. This is inefficient for running OpenQASM 3 programs, - however, and may not be well supported on backends. Instead, the default behaviour - of ``False`` means that individual classical registers will gain their own - ``bit[size] register;`` declarations, and loose :obj:`.Clbit`\\ s will go onto their - own declaration. In this form, each :obj:`.Clbit` must be in either zero or one - :obj:`.ClassicalRegister`\\ s. + alias_classical_registers: If ``True``, then bits may be contained in more than one + register. If so, the registers will be emitted using "alias" definitions, which + might not be well supported by consumers of OpenQASM 3. + + .. seealso:: + Parameter ``allow_aliasing`` + A value for ``allow_aliasing`` overrides any value given here, and + supersedes this parameter. + allow_aliasing: If ``True``, then bits may be contained in more than one register. If + so, the registers will be emitted using "alias" definitions, which might not be + well supported by consumers of OpenQASM 3. Defaults to ``False`` or the value of + ``alias_classical_registers``. + + .. versionadded:: 0.25.0 indent: the indentation string to use for each level within an indented block. Can be set to the empty string to disable indentation. experimental: any experimental features to enable during the export. See @@ -162,7 +167,9 @@ def __init__( """ self.basis_gates = basis_gates self.disable_constants = disable_constants - self.alias_classical_registers = alias_classical_registers + self.allow_aliasing = ( + allow_aliasing if allow_aliasing is not None else (alias_classical_registers or False) + ) self.includes = list(includes) self.indent = indent self.experimental = experimental @@ -180,7 +187,7 @@ def dump(self, circuit, stream): includeslist=self.includes, basis_gates=self.basis_gates, disable_constants=self.disable_constants, - alias_classical_registers=self.alias_classical_registers, + allow_aliasing=self.allow_aliasing, experimental=self.experimental, ) BasicPrinter(stream, indent=self.indent, experimental=self.experimental).visit( @@ -317,6 +324,8 @@ class QASM3Builder: """QASM3 builder constructs an AST from a QuantumCircuit.""" builtins = (Barrier, Measure, Reset, Delay, BreakLoopOp, ContinueLoopOp) + loose_bit_prefix = "_bit" + loose_qubit_prefix = "_qubit" gate_parameter_prefix = "_gate_p" gate_qubit_prefix = "_gate_q" @@ -326,7 +335,7 @@ def __init__( includeslist, basis_gates, disable_constants, - alias_classical_registers, + allow_aliasing, experimental=ExperimentalFeatures(0), ): # This is a stack of stacks; the outer stack is a list of "outer" look-up contexts, and the @@ -347,14 +356,11 @@ def __init__( self._gate_to_declare = {} self._subroutine_to_declare = {} self._opaque_to_declare = {} - self._flat_reg = False - self._physical_qubit = False - self._loose_clbit_index_lookup = {} # An arbitrary counter to help with generation of unique ids for symbol names when there are # clashes (though we generally prefer to keep user names if possible). self._counter = itertools.count() self.disable_constants = disable_constants - self.alias_classical_registers = alias_classical_registers + self.allow_aliasing = allow_aliasing self.global_namespace = GlobalNamespace(includeslist, basis_gates) self.experimental = experimental @@ -409,8 +415,7 @@ def _reserve_variable_name(self, name: ast.Identifier, scope: _Scope) -> ast.Ide the name is already in use. This is useful for autogenerated names that the exporter itself reserves when dealing with - objects that have no standard Terra object backing them, such as the declaration of all - circuit qubits, so cannot be placed into the symbol table by the normal means. + objects that have no standard Terra object backing them. Returns the same identifier, for convenience in chaining.""" table = scope.symbol_map @@ -425,6 +430,8 @@ def _reserve_variable_name(self, name: ast.Identifier, scope: _Scope) -> ast.Ide def _lookup_variable(self, variable) -> ast.Identifier: """Lookup a Terra object 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.current_scope().bit_map[variable] for scope in reversed(self.current_context()): if variable in scope.symbol_map: return scope.symbol_map[variable] @@ -473,12 +480,6 @@ def global_scope(self, assert_=False): ) return self._circuit_ctx[0][0] - def current_outermost_scope(self): - """Return the outermost scope for this context. If building the main program, then this is - the :obj:`.QuantumCircuit` instance that the full program is being built from. If building - a gate or subroutine definition, this is the body that defines the gate or subroutine.""" - return self._circuit_ctx[-1][0] - def current_scope(self): """Return the current circuit scope.""" return self._circuit_ctx[-1][-1] @@ -504,7 +505,7 @@ def push_scope(self, circuit: QuantumCircuit, qubits: Iterable[Qubit], clbits: I f" provided {len(clbits)} clbits to create the mapping." ) mapping = dict(itertools.chain(zip(circuit.qubits, qubits), zip(circuit.clbits, clbits))) - self._circuit_ctx[-1].append(_Scope(circuit, mapping, {})) + self.current_context().append(_Scope(circuit, mapping, {})) def pop_scope(self) -> _Scope: """Pop the current scope (like a ``for`` or ``while`` loop body) off the current context @@ -538,40 +539,15 @@ def build_includes(self): return [ast.Include(filename) for filename in self.includeslist] def build_global_statements(self) -> List[ast.Statement]: - """ - globalStatement - : subroutineDefinition - | kernelDeclaration - | quantumGateDefinition - | calibration - | quantumDeclarationStatement # build_quantumdeclaration - | pragma - ; - - statement - : expressionStatement - | assignmentStatement - | classicalDeclarationStatement - | branchingStatement - | loopStatement - | endStatement - | aliasStatement - | quantumStatement # build_quantuminstruction - ; - """ + """Get a list of the statements that form the global scope of the program.""" definitions = self.build_definitions() # These two "declarations" functions populate stateful variables, since the calls to # `build_quantum_instructions` might also append to those declarations. self.build_parameter_declarations() self.build_classical_declarations() context = self.global_scope(assert_=True).circuit - if getattr(context, "_layout", None) is not None: - self._physical_qubit = True - quantum_declarations = [] - else: - quantum_declarations = self.build_quantum_declarations() + quantum_declarations = self.build_quantum_declarations() quantum_instructions = self.build_quantum_instructions(context.data) - self._physical_qubit = False return [ statement @@ -604,10 +580,7 @@ def build_definition(self, instruction, builder): return instruction._define_qasm3() except AttributeError: pass - self._flat_reg = True - definition = builder(instruction) - self._flat_reg = False - return definition + return builder(instruction) def build_opaque_definition(self, instruction): """Builds an Opaque gate definition as a CalibrationDefinition""" @@ -634,11 +607,13 @@ def build_subroutine_definition(self, instruction): scope = self.current_scope() quantum_arguments = [ ast.QuantumArgument( - self._reserve_variable_name( - ast.Identifier(f"{self.gate_qubit_prefix}_{n_qubit}"), scope + self._register_variable( + qubit, + scope, + self._unique_name(f"{self.gate_qubit_prefix}_{i}", scope), ) ) - for n_qubit in range(len(instruction.definition.qubits)) + for i, qubit in enumerate(instruction.definition.qubits) ] subroutine_body = ast.SubroutineBlock( self.build_quantum_instructions(instruction.definition.data), @@ -666,10 +641,10 @@ def build_gate_signature(self, gate): params.append(self._reserve_variable_name(ast.Identifier(param_name), scope)) params += [self._register_variable(param, scope) for param in definition.parameters] quantum_arguments = [ - self._reserve_variable_name( - ast.Identifier(f"{self.gate_qubit_prefix}_{n_qubit}"), scope + self._register_variable( + qubit, scope, self._unique_name(f"{self.gate_qubit_prefix}_{i}", scope) ) - for n_qubit in range(len(definition.qubits)) + for i, qubit in enumerate(definition.qubits) ] return ast.QuantumGateSignature(ast.Identifier(name), quantum_arguments, params or None) @@ -688,146 +663,116 @@ def build_parameter_declarations(self): else: self._global_classical_declarations.append(declaration) - @property - def base_classical_register_name(self): - """The base register name""" - name = "_all_clbits" if self.alias_classical_registers else "_loose_clbits" - if name in self.global_namespace._data: - raise NotImplementedError # TODO choose a different name if there is a name collision - return name - - @property - def base_quantum_register_name(self): - """The base register name""" - name = "_all_qubits" - if name in self.global_namespace._data: - raise NotImplementedError # TODO choose a different name if there is a name collision - return name - def build_classical_declarations(self): """Extend the global classical declarations with AST nodes declaring all the classical bits and registers. - The behaviour of this function depends on the setting ``alias_classical_registers``. If this + The behaviour of this function depends on the setting ``allow_aliasing``. If this is ``True``, then the output will be in the same form as the output of :meth:`.build_classical_declarations`, with the registers being aliases. If ``False``, it will instead return a :obj:`.ast.ClassicalDeclaration` for each classical register, and one for the loose :obj:`.Clbit` instances, and will raise :obj:`QASM3ExporterError` if any registers overlap. - - This function populates the lookup table ``self._loose_clbit_index_lookup``. """ - global_scope = self.global_scope() - circuit = self.current_scope().circuit - if self.alias_classical_registers: - self._loose_clbit_index_lookup = { - bit: index for index, bit in enumerate(circuit.clbits) - } - self._global_classical_declarations.append( - self.build_clbit_declaration( - len(circuit.clbits), - self._reserve_variable_name( - ast.Identifier(self.base_classical_register_name), global_scope - ), - ) - ) - self._global_classical_declarations.extend(self.build_aliases(circuit.cregs)) - return - loose_register_size = 0 - for index, bit in enumerate(circuit.clbits): - found_bit = circuit.find_bit(bit) - if len(found_bit.registers) > 1: + scope = self.global_scope(assert_=True) + if any(len(scope.circuit.find_bit(q).registers) > 1 for q in scope.circuit.clbits): + # There are overlapping registers, so we need to use aliases to emit the structure. + if not self.allow_aliasing: raise QASM3ExporterError( - f"Clbit {index} is in multiple registers, but 'alias_classical_registers' is" - f" False. Registers and indices: {found_bit.registers}." + "Some classical registers in this circuit overlap and need aliases to express," + " but 'allow_aliasing' is false." ) - if not found_bit.registers: - self._loose_clbit_index_lookup[bit] = loose_register_size - loose_register_size += 1 - if loose_register_size > 0: - self._global_classical_declarations.append( - self.build_clbit_declaration( - loose_register_size, - self._reserve_variable_name( - ast.Identifier(self.base_classical_register_name), global_scope + clbits = ( + ast.ClassicalDeclaration( + ast.BitType(), + self._register_variable( + clbit, scope, self._unique_name(f"{self.loose_bit_prefix}{i}", scope) ), ) + for i, clbit in enumerate(scope.circuit.clbits) ) + self._global_classical_declarations.extend(clbits) + self._global_classical_declarations.extend(self.build_aliases(scope.circuit.cregs)) + return + # If we're here, we're in the clbit happy path where there are no clbits that are in more + # than one register. We can output things very naturally. self._global_classical_declarations.extend( - self.build_clbit_declaration( - len(register), self._register_variable(register, global_scope) + ast.ClassicalDeclaration( + ast.BitType(), + self._register_variable( + clbit, scope, self._unique_name(f"{self.loose_bit_prefix}{i}", scope) + ), ) - for register in circuit.cregs + for i, clbit in enumerate(scope.circuit.clbits) + if not scope.circuit.find_bit(clbit).registers ) - - def build_clbit_declaration( - self, n_clbits: int, name: ast.Identifier - ) -> ast.ClassicalDeclaration: - """Return a declaration of the :obj:`.Clbit`\\ s as a ``bit[n]``.""" - return ast.ClassicalDeclaration(ast.BitArrayType(n_clbits), name) + for register in scope.circuit.cregs: + name = self._register_variable(register, scope) + for i, bit in enumerate(register): + scope.symbol_map[bit] = ast.SubscriptedIdentifier(name, ast.Integer(i)) + self._global_classical_declarations.append( + ast.ClassicalDeclaration(ast.BitArrayType(len(register)), name) + ) def build_quantum_declarations(self): """Return a list of AST nodes declaring all the qubits in the current scope, and all the alias declarations for these qubits.""" - return [self.build_qubit_declarations()] + self.build_aliases( - self.current_scope().circuit.qregs - ) - - def build_qubit_declarations(self): - """Return a declaration of all the :obj:`.Qubit`\\ s in the current scope.""" - global_scope = self.global_scope(assert_=True) - # Base register - return ast.QuantumDeclaration( - self._reserve_variable_name( - ast.Identifier(self.base_quantum_register_name), global_scope - ), - ast.Designator(self.build_integer(self.current_scope().circuit.num_qubits)), - ) + scope = self.global_scope(assert_=True) + if scope.circuit.layout is not None: + # We're referring to physical qubits. These can't be declared in OQ3, but we need to + # track the bit -> expression mapping in our symbol table. + for i, bit in enumerate(scope.circuit.qubits): + scope.symbol_map[bit] = ast.PhysicalQubitIdentifier(ast.Identifier(str(i))) + return [] + if any(len(scope.circuit.find_bit(q).registers) > 1 for q in scope.circuit.qubits): + # There are overlapping registers, so we need to use aliases to emit the structure. + if not self.allow_aliasing: + raise QASM3ExporterError( + "Some quantum registers in this circuit overlap and need aliases to express," + " but 'allow_aliasing' is false." + ) + qubits = [ + ast.QuantumDeclaration( + self._register_variable( + qubit, scope, self._unique_name(f"{self.loose_qubit_prefix}{i}", scope) + ) + ) + for i, qubit in enumerate(scope.circuit.qubits) + ] + return qubits + self.build_aliases(scope.circuit.qregs) + # If we're here, we're in the virtual-qubit happy path where there are no qubits that are in + # more than one register. We can output things very naturally. + loose_qubits = [ + ast.QuantumDeclaration( + self._register_variable( + qubit, scope, self._unique_name(f"{self.loose_qubit_prefix}{i}", scope) + ) + ) + for i, qubit in enumerate(scope.circuit.qubits) + if not scope.circuit.find_bit(qubit).registers + ] + registers = [] + for register in scope.circuit.qregs: + name = self._register_variable(register, scope) + for i, bit in enumerate(register): + scope.symbol_map[bit] = ast.SubscriptedIdentifier(name, ast.Integer(i)) + registers.append( + ast.QuantumDeclaration(name, ast.Designator(ast.Integer(len(register)))) + ) + return loose_qubits + registers def build_aliases(self, registers: Iterable[Register]) -> List[ast.AliasStatement]: """Return a list of alias declarations for the given registers. The registers can be either classical or quantum.""" - out = [] scope = self.current_scope() + out = [] for register in registers: - elements = [] - # Greedily consolidate runs of bits into ranges. We don't bother trying to handle - # steps; there's no need in generated code. Even single bits are referenced as ranges - # because the concatenation in an alias statement can only concatenate arraylike values. - start_index, prev_index = None, None - register_identifier = ( - ast.Identifier(self.base_quantum_register_name) - if isinstance(register, QuantumRegister) - else ast.Identifier(self.base_classical_register_name) - ) - for bit in register: - cur_index = self.find_bit(bit).index - if start_index is None: - start_index = cur_index - elif cur_index != prev_index + 1: - elements.append( - ast.SubscriptedIdentifier( - register_identifier, - ast.Range( - start=self.build_integer(start_index), - end=self.build_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(register) != 0: - elements.append( - ast.SubscriptedIdentifier( - register_identifier, - ast.Range( - start=self.build_integer(start_index), - end=self.build_integer(prev_index), - ), - ) - ) - out.append(ast.AliasStatement(self._register_variable(register, scope), elements)) + name = self._register_variable(register, scope) + elements = [self._lookup_variable(bit) for bit in register] + for i, bit in enumerate(register): + # This might shadow previous definitions, but that's not a problem. + scope.symbol_map[bit] = ast.SubscriptedIdentifier(name, ast.Integer(i)) + out.append(ast.AliasStatement(name, ast.IndexSet(elements))) return out def build_quantum_instructions(self, instructions): @@ -850,19 +795,17 @@ def build_quantum_instructions(self, instructions): if isinstance(instruction.operation, Gate): nodes = [self.build_gate_call(instruction)] elif isinstance(instruction.operation, Barrier): - operands = [ - self.build_single_bit_reference(operand) for operand in instruction.qubits - ] + operands = [self._lookup_variable(operand) for operand in instruction.qubits] nodes = [ast.QuantumBarrier(operands)] elif isinstance(instruction.operation, Measure): measurement = ast.QuantumMeasurement( - [self.build_single_bit_reference(operand) for operand in instruction.qubits] + [self._lookup_variable(operand) for operand in instruction.qubits] ) - qubit = self.build_single_bit_reference(instruction.clbits[0]) + qubit = self._lookup_variable(instruction.clbits[0]) nodes = [ast.QuantumMeasurementAssignment(qubit, measurement)] elif isinstance(instruction.operation, Reset): nodes = [ - ast.QuantumReset(self.build_single_bit_reference(operand)) + ast.QuantumReset(self._lookup_variable(operand)) for operand in instruction.qubits ] elif isinstance(instruction.operation, Delay): @@ -911,7 +854,7 @@ def build_switch_statement( " argument of the exporter." ) if isinstance(instruction.operation.target, Clbit): - target = self.build_single_bit_reference(instruction.operation.target) + target = self._lookup_variable(instruction.operation.target) else: real_target = self._lookup_variable(instruction.operation.target) global_scope = self.global_scope() @@ -995,7 +938,7 @@ def build_delay(self, instruction: CircuitInstruction) -> ast.QuantumDelay: } duration = ast.DurationLiteral(duration_value, unit_map[unit]) return ast.QuantumDelay( - duration, [self.build_single_bit_reference(qubit) for qubit in instruction.qubits] + duration, [self._lookup_variable(qubit) for qubit in instruction.qubits] ) def build_integer(self, value) -> ast.Integer: @@ -1014,12 +957,10 @@ def build_program_block(self, instructions): def build_eqcondition(self, condition): """Classical Conditional condition from a instruction.condition""" - if isinstance(condition[0], Clbit): - condition_on = self.build_single_bit_reference(condition[0]) - else: - condition_on = self._lookup_variable(condition[0]) return ast.ComparisonExpression( - condition_on, ast.EqualsOperator(), self.build_integer(condition[1]) + self._lookup_variable(condition[0]), + ast.EqualsOperator(), + self.build_integer(condition[1]), ) def _rebind_scoped_parameters(self, expression): @@ -1043,7 +984,7 @@ def build_gate_call(self, instruction: CircuitInstruction): gate_name = ast.Identifier("U") else: gate_name = ast.Identifier(self.global_namespace[instruction.operation]) - qubits = [self.build_single_bit_reference(qubit) for qubit in instruction.qubits] + qubits = [self._lookup_variable(qubit) for qubit in instruction.qubits] if self.disable_constants: parameters = [ ast.Expression(self._rebind_scoped_parameters(param)) @@ -1063,43 +1004,9 @@ def build_subroutine_call(self, instruction: CircuitInstruction): expressions = [ast.Expression(param) for param in instruction.operation.params] # 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. - bits = [self.build_single_bit_reference(bit) for bit in instruction.qubits] + bits = [self._lookup_variable(bit) for bit in instruction.qubits] return ast.SubroutineCall(identifier, bits, expressions) - def build_single_bit_reference(self, bit: Bit) -> ast.Identifier: - """Get an identifier node that refers to one particular bit.""" - found_bit = self.find_bit(bit) - if self._physical_qubit and isinstance(bit, Qubit): - return ast.PhysicalQubitIdentifier(ast.Identifier(str(found_bit.index))) - if self._flat_reg: - return ast.Identifier(f"{self.gate_qubit_prefix}_{found_bit.index}") - if found_bit.registers: - # We preferentially return a reference via a register in the hope that this is what the - # user is used to seeing as well. - register, index = found_bit.registers[0] - return ast.SubscriptedIdentifier( - self._lookup_variable(register), self.build_integer(index) - ) - # Otherwise reference via the list of all qubits, or the list of loose clbits. - if isinstance(bit, Qubit): - return ast.SubscriptedIdentifier( - ast.Identifier(self.base_quantum_register_name), self.build_integer(found_bit.index) - ) - return ast.SubscriptedIdentifier( - ast.Identifier(self.base_classical_register_name), - self.build_integer(self._loose_clbit_index_lookup[bit]), - ) - - def find_bit(self, bit: Bit): - """Look up the bit using :meth:`.QuantumCircuit.find_bit` in the current outermost scope.""" - # This is a hacky work-around for now. Really this should be a proper symbol-table lookup, - # but with us expecting to put in a whole new AST for Terra 0.20, this should be sufficient - # for the use-cases we support. (Jake, 2021-11-22.) - if len(self.current_context()) > 1: - ancestor_bit = self.current_scope().bit_map[bit] - return self.current_outermost_scope().circuit.find_bit(ancestor_bit) - return self.current_scope().circuit.find_bit(bit) - def _infer_variable_declaration( circuit: QuantumCircuit, parameter: Parameter, parameter_name: ast.Identifier diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py index e97403c60b02..9a30b45c0899 100644 --- a/qiskit/qasm3/printer.py +++ b/qiskit/qasm3/printer.py @@ -171,6 +171,9 @@ def _visit_IntType(self, node: ast.IntType) -> None: if node.size is not None: self.stream.write(f"[{node.size}]") + def _visit_BitType(self, _node: ast.BitType) -> None: + self.stream.write("bit") + def _visit_BitArrayType(self, node: ast.BitArrayType) -> None: self.stream.write(f"bit[{node.size}]") @@ -271,7 +274,8 @@ def _visit_IODeclaration(self, node: ast.IODeclaration) -> None: def _visit_QuantumDeclaration(self, node: ast.QuantumDeclaration) -> None: self._start_line() self.stream.write("qubit") - self.visit(node.designator) + if node.designator is not None: + self.visit(node.designator) self.stream.write(" ") self.visit(node.identifier) self._end_statement() @@ -281,7 +285,7 @@ def _visit_AliasStatement(self, node: ast.AliasStatement) -> None: self.stream.write("let ") self.visit(node.identifier) self.stream.write(" = ") - self._visit_sequence(node.concatenation, separator=" ++ ") + self.visit(node.value) self._end_statement() def _visit_QuantumGateModifier(self, node: ast.QuantumGateModifier) -> None: diff --git a/releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml b/releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml new file mode 100644 index 000000000000..d539bd4515f9 --- /dev/null +++ b/releasenotes/notes/qasm3-alias-refactor-3389bfce3e29e4cf.yaml @@ -0,0 +1,15 @@ +--- +features: + - | + The OpenQASM 3 exporters (:func:`.qasm3.dump`, :func:`~.qasm3.dumps` and :class:`~.qasm3.Exporter`) + have a new ``allow_aliasing`` argument, which will eventually replace the ``alias_classical_registers`` + argument. This controls whether aliasing is permitted for either classical bits or qubits, rather + than the option only being available for classical bits. +upgrade: + - | + The OpenQASM 3 exporters (:func:`.qasm3.dump`, :func:`~.qasm3.dumps` and :class:`~.qasm3.Exporter`) + will now use fewer "register alias" definitions in its output. The circuit described will not + change, but it will now preferentially export in terms of direct ``bit``, ``qubit`` and + ``qubit[n]`` types rather than producing a ``_loose_bits`` register and aliasing more registers + off this. This is done to minimise the number of advanced OpenQASM 3 features in use, and to + avoid introducing unnecessary array structure into programmes that do not require it. diff --git a/test/python/qasm3/test_export.py b/test/python/qasm3/test_export.py index 9978426e6d9e..1e81d1721a3c 100644 --- a/test/python/qasm3/test_export.py +++ b/test/python/qasm3/test_export.py @@ -46,8 +46,7 @@ def setUp(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "U(2*pi, 3*pi, -5*pi) q[0];", "", ] @@ -81,7 +80,7 @@ def setUpClass(cls): # space (`\s`) or semicolon rather than the end-of-word `\b` because we want to ensure that # the exporter isn't putting out invalid characters as part of the identifiers. cls.register_regex = re.compile( - r"^\s*(let|bit(\[\d+\])?)\s+(?P\w+)[\s;]", re.U | re.M + r"^\s*(let|(qu)?bit(\[\d+\])?)\s+(?P\w+)[\s;]", re.U | re.M ) scalar_type_names = { "angle", @@ -116,9 +115,8 @@ def test_regs_conds_qasm(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[3] cr;", - "qubit[3] _all_qubits;", - "let qr1 = _all_qubits[0:0];", - "let qr2 = _all_qubits[1:2];", + "qubit[1] qr1;", + "qubit[2] qr2;", "cr[0] = measure qr1[0];", "cr[1] = measure qr2[0];", "cr[2] = measure qr2[1];", @@ -148,16 +146,24 @@ def test_registers_as_aliases(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[10] _all_qubits;", - "let first_four = _all_qubits[0:3];", - "let last_five = _all_qubits[5:9];", - # The exporter does not attempt to output steps. - "let alternate = _all_qubits[0:0] ++ _all_qubits[2:2] ++ _all_qubits[4:4] ++ _all_qubits[6:6] ++ _all_qubits[8:8];", - "let sporadic = _all_qubits[4:4] ++ _all_qubits[2:2] ++ _all_qubits[9:9];", + "qubit _qubit0;", + "qubit _qubit1;", + "qubit _qubit2;", + "qubit _qubit3;", + "qubit _qubit4;", + "qubit _qubit5;", + "qubit _qubit6;", + "qubit _qubit7;", + "qubit _qubit8;", + "qubit _qubit9;", + "let first_four = {_qubit0, _qubit1, _qubit2, _qubit3};", + "let last_five = {_qubit5, _qubit6, _qubit7, _qubit8, _qubit9};", + "let alternate = {first_four[0], first_four[2], _qubit4, last_five[1], last_five[3]};", + "let sporadic = {alternate[2], alternate[1], last_five[4]};", "", ] ) - self.assertEqual(Exporter().dumps(qc), expected_qasm) + self.assertEqual(Exporter(allow_aliasing=True).dumps(qc), expected_qasm) def test_composite_circuit(self): """Test with a composite circuit instruction and barriers""" @@ -187,8 +193,7 @@ def test_composite_circuit(self): " cx _gate_q_0, _gate_q_1;", "}", "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "h qr[0];", "cx qr[0], qr[1];", "barrier qr[0], qr[1];", @@ -228,8 +233,7 @@ def test_custom_gate(self): " cx _gate_q_0, _gate_q_1;", "}", "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "h qr[0];", "cx qr[0], qr[1];", "barrier qr[0], qr[1];", @@ -270,8 +274,7 @@ def test_same_composite_circuits(self): " cx _gate_q_0, _gate_q_1;", "}", "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "h qr[0];", "cx qr[0], qr[1];", "barrier qr[0], qr[1];", @@ -319,8 +322,7 @@ def test_composite_circuits_with_same_name(self): f"def my_gate_{my_gate_inst3_id}(qubit _gate_q_0) {{", " x _gate_q_0;", "}", - "qubit[1] _all_qubits;", - "let qr = _all_qubits[0:0];", + "qubit[1] qr;", "my_gate qr[0];", f"my_gate_{my_gate_inst2_id} qr[0];", f"my_gate_{my_gate_inst3_id} qr[0];", @@ -337,8 +339,7 @@ def test_pi_disable_constants_false(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "U(2*pi, 3*pi, -5*pi) q[0];", "", ] @@ -353,8 +354,7 @@ def test_pi_disable_constants_true(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "U(6.283185307179586, 9.42477796076938, -15.707963267948966) q[0];", "", ] @@ -378,8 +378,7 @@ def test_custom_gate_with_unbound_parameter(self): "gate custom(a) _gate_q_0 {", " rx(a) _gate_q_0;", "}", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", "custom(a) q[0];", "", ] @@ -405,8 +404,7 @@ def test_custom_gate_with_bound_parameter(self): "gate custom _gate_q_0 {", " rx(0.5) _gate_q_0;", "}", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", "custom q[0];", "", ] @@ -438,9 +436,8 @@ def test_custom_gate_with_params_bound_main_call(self): " rz(pi) _gate_q_0;", " rz(pi/4) _gate_q_1;", "}", - "qubit[6] _all_qubits;", - "let q = _all_qubits[0:2];", - "let r = _all_qubits[3:5];", + "qubit[3] q;", + "qubit[3] r;", "custom(pi, pi/2) q[0], r[0];", "", ] @@ -471,8 +468,7 @@ def test_reused_custom_parameter(self): f"gate {circuit_name_1} _gate_q_0 {{", " rx(1.0) _gate_q_0;", "}", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", f"{circuit_name_0} q[0];", f"{circuit_name_1} q[0];", "", @@ -490,8 +486,7 @@ def test_unbound_circuit(self): "OPENQASM 3;", 'include "stdgates.inc";', "input float[64] θ;", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", "rz(θ) q[0];", "", ] @@ -519,8 +514,7 @@ def test_unknown_parameterized_gate_called_multiple_times(self): " cx _gate_q_0, _gate_q_1;", " h _gate_q_1;", "}", - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "rzx(x) q[0], q[1];", "rzx(y) q[0], q[1];", "rzx(0.5) q[0], q[1];", @@ -546,8 +540,7 @@ def test_gate_qasm_with_ctrl_state(self): " ch _gate_q_0, _gate_q_1;", " x _gate_q_0;", "}", - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "ch_o0 q[0], q[1];", "", ] @@ -570,8 +563,7 @@ def test_custom_gate_collision_with_stdlib(self): f"gate cx_{custom_gate_id} _gate_q_0, _gate_q_1 {{", " cx _gate_q_0, _gate_q_1;", "}", - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", f"cx_{custom_gate_id} q[0], q[1];", "", ] @@ -615,8 +607,7 @@ def test_no_include(self): " h _gate_q_0;", " sdg _gate_q_0;", "}", - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", "rz(pi/2) q[0];", "sx q[0];", "cx q[0], q[1];", @@ -778,8 +769,7 @@ def test_reset_statement(self): "def inner_gate(qubit _gate_q_0) {", " reset _gate_q_0;", "}", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "reset qr[0];", "inner_gate qr[1];", "", @@ -803,8 +793,7 @@ def test_delay_statement(self): "def inner_gate(qubit _gate_q_0) {", " delay[50dt] _gate_q_0;", "}", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "delay[100ms] qr[0];", "delay[2000ns] qr[1];", "inner_gate qr[1];", @@ -829,10 +818,11 @@ def test_loose_qubits(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[4] _all_qubits;", - "let qr = _all_qubits[2:3];", - "h _all_qubits[0];", - "h _all_qubits[1];", + "qubit _qubit0;", + "qubit _qubit1;", + "qubit[2] qr;", + "h _qubit0;", + "h _qubit1;", "h qr[0];", "h qr[1];", "", @@ -859,24 +849,25 @@ def test_loose_clbits(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "bit[3] _loose_clbits;", + "bit _bit0;", + "bit _bit3;", + "bit _bit6;", "bit[2] cr1;", "bit[2] cr2;", - "qubit[1] _all_qubits;", - "let qr = _all_qubits[0:0];", - "_loose_clbits[0] = measure qr[0];", + "qubit[1] qr;", + "_bit0 = measure qr[0];", "cr1[0] = measure qr[0];", "cr1[1] = measure qr[0];", - "_loose_clbits[1] = measure qr[0];", + "_bit3 = measure qr[0];", "cr2[0] = measure qr[0];", "cr2[1] = measure qr[0];", - "_loose_clbits[2] = measure qr[0];", + "_bit6 = measure qr[0];", "", ] ) self.assertEqual(dumps(qc), expected_qasm) - def test_alias_classical_registers(self): + def test_classical_register_aliasing(self): """Test that clbits that are not in any register can be used without issue.""" qreg = QuantumRegister(1, name="qr") bits = [Clbit() for _ in [None] * 7] @@ -897,18 +888,68 @@ def test_alias_classical_registers(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "bit[7] _all_clbits;", - "let cr1 = _all_clbits[1:2];", - "let cr2 = _all_clbits[4:5];", - "let cr3 = _all_clbits[5:6];", - "qubit[1] _all_qubits;", - "let qr = _all_qubits[0:0];", - "_all_clbits[0] = measure qr[0];", + "bit _bit0;", + "bit _bit1;", + "bit _bit2;", + "bit _bit3;", + "bit _bit4;", + "bit _bit5;", + "bit _bit6;", + "let cr1 = {_bit1, _bit2};", + "let cr2 = {_bit4, _bit5};", + "let cr3 = {cr2[1], _bit6};", + "qubit[1] qr;", + "_bit0 = measure qr[0];", "cr1[0] = measure qr[0];", "cr1[1] = measure qr[0];", - "_all_clbits[3] = measure qr[0];", + "_bit3 = measure qr[0];", "cr2[0] = measure qr[0];", - "cr2[1] = measure qr[0];", + "cr3[0] = measure qr[0];", + "cr3[1] = measure qr[0];", + "", + ] + ) + self.assertEqual(dumps(qc, allow_aliasing=True), expected_qasm) + + def test_old_alias_classical_registers_option(self): + """Test that the ``alias_classical_registers`` option still functions during its changeover + period.""" + qreg = QuantumRegister(1, name="qr") + bits = [Clbit() for _ in [None] * 7] + cr1 = ClassicalRegister(name="cr1", bits=bits[1:3]) + cr2 = ClassicalRegister(name="cr2", bits=bits[4:6]) + # cr3 overlaps cr2, but this should be allowed in this alias form. + cr3 = ClassicalRegister(name="cr3", bits=bits[5:]) + qc = QuantumCircuit(bits, qreg, cr1, cr2, cr3) + qc.measure(0, 0) + qc.measure(0, 1) + qc.measure(0, 2) + qc.measure(0, 3) + qc.measure(0, 4) + qc.measure(0, 5) + qc.measure(0, 6) + + expected_qasm = "\n".join( + [ + "OPENQASM 3;", + 'include "stdgates.inc";', + "bit _bit0;", + "bit _bit1;", + "bit _bit2;", + "bit _bit3;", + "bit _bit4;", + "bit _bit5;", + "bit _bit6;", + "let cr1 = {_bit1, _bit2};", + "let cr2 = {_bit4, _bit5};", + "let cr3 = {cr2[1], _bit6};", + "qubit[1] qr;", + "_bit0 = measure qr[0];", + "cr1[0] = measure qr[0];", + "cr1[1] = measure qr[0];", + "_bit3 = measure qr[0];", + "cr2[0] = measure qr[0];", + "cr3[0] = measure qr[0];", "cr3[1] = measure qr[0];", "", ] @@ -933,8 +974,7 @@ def test_simple_for_loop(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - f"let {qr_name} = _all_qubits[0:1];", + f"qubit[2] {qr_name};", f"for {parameter.name} in {{0, 3, 4}} {{", f" rx({parameter.name}) {qr_name}[1];", " break;", @@ -973,8 +1013,7 @@ def test_nested_for_loop(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - f"let {qr_name} = _all_qubits[0:1];", + f"qubit[2] {qr_name};", f"for {outer_parameter.name} in [0:3] {{", f" h {qr_name}[0];", f" rz({outer_parameter.name}) {qr_name}[1];", @@ -1023,8 +1062,7 @@ def test_regular_parameter_in_nested_for_loop(self): 'include "stdgates.inc";', # This next line will be missing until gh-7280 is fixed. f"input float[64] {regular_parameter.name};", - "qubit[2] _all_qubits;", - f"let {qr_name} = _all_qubits[0:1];", + f"qubit[2] {qr_name};", f"for {outer_parameter.name} in [0:3] {{", f" h {qr_name}[0];", f" h {qr_name}[1];", @@ -1055,8 +1093,7 @@ def test_for_loop_with_no_parameter(self): [ "OPENQASM 3;", 'include "stdgates.inc";', - "qubit[2] _all_qubits;", - f"let {qr_name} = _all_qubits[0:1];", + f"qubit[2] {qr_name};", "for _ in {0, 3, 4} {", f" h {qr_name}[1];", "}", @@ -1083,8 +1120,7 @@ def test_simple_while_loop(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "while (cr == 0) {", " h qr[1];", " break;", @@ -1121,8 +1157,7 @@ def test_nested_while_loop(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "while (cr == 0) {", " cr[0] = measure qr[0];", " cr[1] = measure qr[1];", @@ -1156,8 +1191,7 @@ def test_simple_if_statement(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "if (cr == 0) {", " h qr[1];", "}", @@ -1185,8 +1219,7 @@ def test_simple_if_else_statement(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "if (cr == 0) {", " h qr[1];", "} else {", @@ -1225,8 +1258,7 @@ def test_nested_if_else_statement(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "if (cr == 0) {", " if (cr[0] == 0) {", " cr[0] = measure qr[0];", @@ -1273,8 +1305,7 @@ def test_chain_else_if(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "if (cr == 0) {", " if (cr[0] == 0) {", " cr[0] = measure qr[0];", @@ -1295,7 +1326,7 @@ def test_chain_else_if(self): includeslist=("stdgates.inc",), basis_gates=("U",), disable_constants=False, - alias_classical_registers=False, + allow_aliasing=False, ) stream = StringIO() BasicPrinter(stream, indent=" ", chain_else_if=True).visit(builder.build_program()) @@ -1331,8 +1362,7 @@ def test_chain_else_if_does_not_chain_if_extra_instructions(self): "OPENQASM 3;", 'include "stdgates.inc";', "bit[2] cr;", - "qubit[2] _all_qubits;", - "let qr = _all_qubits[0:1];", + "qubit[2] qr;", "if (cr == 0) {", " if (cr[0] == 0) {", " cr[0] = measure qr[0];", @@ -1356,7 +1386,7 @@ def test_chain_else_if_does_not_chain_if_extra_instructions(self): includeslist=("stdgates.inc",), basis_gates=("U",), disable_constants=False, - alias_classical_registers=False, + allow_aliasing=False, ) stream = StringIO() BasicPrinter(stream, indent=" ", chain_else_if=True).visit(builder.build_program()) @@ -1386,8 +1416,7 @@ def test_custom_gate_used_in_loop_scope(self): "gate custom _gate_q_0 {", " rx(0.5) _gate_q_0;", "}", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", "for b in [0:1] {", " custom q[0];", "}", @@ -1428,8 +1457,7 @@ def test_parameter_expression_after_naming_escape(self): "OPENQASM 3;", 'include "stdgates.inc";', "input float[64] _measure;", - "qubit[1] _all_qubits;", - "let q = _all_qubits[0:0];", + "qubit[1] q;", "U(2*_measure, 0, 0) q[0];", "", ] @@ -1522,8 +1550,7 @@ def test_basis_gates(self): f" u3_{id(u3_2)}(pi, 0, pi) _gate_q_0;", "}", "bit[2] c;", - "qubit[3] _all_qubits;", - "let q = _all_qubits[0:2];", + "qubit[3] q;", "h q[1];", "cx q[1], q[2];", "barrier q[0], q[1], q[2];", @@ -1647,9 +1674,8 @@ def test_custom_gate_with_params_bound_main_call(self): " rz(pi) _gate_q_0;", " rz(pi/4) _gate_q_1;", "}", - "qubit[6] _all_qubits;", - "let q = _all_qubits[0:2];", - "let r = _all_qubits[3:5];", + "qubit[3] q;", + "qubit[3] r;", f"custom_{custom_id}(pi, pi/2) q[0], r[0];", "", ] @@ -1712,8 +1738,7 @@ def test_no_include(self): " h _gate_q_0;", " sdg _gate_q_0;", "}", - "qubit[2] _all_qubits;", - "let q = _all_qubits[0:1];", + "qubit[2] q;", f"rz_{id(rz)}(pi/2) q[0];", "sx q[0];", "cx q[0], q[1];", @@ -1747,8 +1772,7 @@ def test_unusual_conditions(self): OPENQASM 3; include "stdgates.inc"; bit[2] c; -qubit[3] _all_qubits; -let q = _all_qubits[0:2]; +qubit[3] q; h q[0]; c[0] = measure q[0]; if (c[0] == 1) { @@ -1805,15 +1829,15 @@ def test_switch_clbit(self): expected = """\ OPENQASM 3; include "stdgates.inc"; -bit[1] _loose_clbits; -qubit[1] _all_qubits; -switch (_loose_clbits[0]) { +bit _bit0; +qubit _qubit0; +switch (_bit0) { case 1: { - x _all_qubits[0]; + x _qubit0; } break; case 0: { - z _all_qubits[0]; + z _qubit0; } break; } @@ -1840,19 +1864,19 @@ def test_switch_register(self): include "stdgates.inc"; bit[2] c; int switch_dummy; -qubit[1] _all_qubits; +qubit _qubit0; switch_dummy = c; switch (switch_dummy) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: { - y _all_qubits[0]; + y _qubit0; } break; case 2: { - z _all_qubits[0]; + z _qubit0; } break; } @@ -1879,19 +1903,19 @@ def test_switch_with_default(self): include "stdgates.inc"; bit[2] c; int switch_dummy; -qubit[1] _all_qubits; +qubit _qubit0; switch_dummy = c; switch (switch_dummy) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: { - y _all_qubits[0]; + y _qubit0; } break; default: { - z _all_qubits[0]; + z _qubit0; } break; } @@ -1917,16 +1941,16 @@ def test_switch_multiple_cases_to_same_block(self): include "stdgates.inc"; bit[2] c; int switch_dummy; -qubit[1] _all_qubits; +qubit _qubit0; switch_dummy = c; switch (switch_dummy) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: case 2: { - y _all_qubits[0]; + y _qubit0; } break; } @@ -1954,28 +1978,28 @@ def test_multiple_switches_dont_clash_on_dummy(self): bit[2] switch_dummy; int switch_dummy__generated0; int switch_dummy__generated1; -qubit[1] _all_qubits; +qubit _qubit0; switch_dummy__generated0 = switch_dummy; switch (switch_dummy__generated0) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: case 2: { - y _all_qubits[0]; + y _qubit0; } break; } switch_dummy__generated1 = switch_dummy; switch (switch_dummy__generated1) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: case 2: { - y _all_qubits[0]; + y _qubit0; } break; } @@ -2007,17 +2031,17 @@ def test_switch_nested_in_if(self): bit[2] c; int switch_dummy; int switch_dummy__generated0; -qubit[1] _all_qubits; +qubit _qubit0; if (c == 1) { switch_dummy = c; switch (switch_dummy) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: case 2: { - y _all_qubits[0]; + y _qubit0; } break; } @@ -2025,12 +2049,12 @@ def test_switch_nested_in_if(self): switch_dummy__generated0 = c; switch (switch_dummy__generated0) { case 0: { - x _all_qubits[0]; + x _qubit0; } break; case 1: case 2: { - y _all_qubits[0]; + y _qubit0; } break; } @@ -2051,7 +2075,7 @@ def test_disallow_overlapping_classical_registers_if_no_aliasing(self): registers = [ClassicalRegister(bits=clbits[:4]), ClassicalRegister(bits=clbits[1:])] qc = QuantumCircuit(qubits, *registers) exporter = Exporter(alias_classical_registers=False) - with self.assertRaisesRegex(QASM3ExporterError, r"Clbit .* is in multiple registers.*"): + with self.assertRaisesRegex(QASM3ExporterError, r"classical registers .* overlap"): exporter.dumps(qc) @data([1, 2, 1.1], [1j, 2])