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])