Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/autoqasm/instructions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ def bell():
"""

from .gates import *
from .instructions import reset # noqa: F401
from .instructions import barrier, reset # noqa: F401
from .measurements import measure, measure_ff # noqa: F401
from .qubits import global_qubit_register # noqa: F401
24 changes: 19 additions & 5 deletions src/autoqasm/instructions/instructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

from autoqasm import program as aq_program
from autoqasm import types as aq_types
from autoqasm.instructions.qubits import _qubit
from autoqasm.instructions.qubits import _as_qubit_iterable, _qubit
from autoqasm.types import QubitIdentifierType
from braket.circuits.basis_state import BasisState, BasisStateInput

Expand Down Expand Up @@ -101,10 +101,7 @@ def _get_pos_neg_control(
contains the positive-control qubits, and the second list contains the negative-control
qubits. The union of the two lists is the same as the list of control qubits.
"""
if control is None:
control = []
elif aq_types.is_qubit_identifier_type(control):
control = [control]
control = _as_qubit_iterable(control)

if control_state is None:
control_state = [1] * len(control)
Expand All @@ -123,3 +120,20 @@ def reset(target: QubitIdentifierType) -> None:
target (QubitIdentifierType): The target qubit.
"""
_qubit_instruction("reset", [target], is_unitary=False)


def barrier(
qubits: aq_types.QubitIdentifierType | Iterable[aq_types.QubitIdentifierType] | None = None,
) -> None:
"""Adds a barrier compiler directive on the specified qubits. If ``qubits`` is None,
the barrier applies to all qubits in the program.

Args:
qubits (QubitIdentifierType | Iterable[QubitIdentifierType] | None): The target qubits.
If None, the barrier applies to all qubits in the program. Default is None.
"""
qubits = _as_qubit_iterable(qubits)
program_conversion_context = aq_program.get_program_conversion_context()
program_conversion_context.validate_gate_targets(qubits, [])
program_conversion_context.register_gate("barrier", is_compiler_directive=True)
program_conversion_context.get_oqpy_program().barrier([_qubit(q) for q in qubits])
8 changes: 2 additions & 6 deletions src/autoqasm/instructions/measurements.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def my_program():
from autoqasm import program
from autoqasm import types as aq_types
from autoqasm.instructions.instructions import _qubit_instruction
from autoqasm.instructions.qubits import GlobalQubitRegister, _qubit, global_qubit_register
from autoqasm.instructions.qubits import _as_qubit_iterable, _qubit, global_qubit_register


def measure(
Expand All @@ -45,11 +45,7 @@ def measure(
Returns:
BitVar: Bit variable the measurement results are assigned to.
"""
if qubits is None:
qubits = global_qubit_register()

if aq_types.is_qubit_identifier_type(qubits) and not isinstance(qubits, GlobalQubitRegister):
qubits = [qubits]
qubits = _as_qubit_iterable(qubits, default=global_qubit_register())

oqpy_program = program.get_program_conversion_context().get_oqpy_program()

Expand Down
17 changes: 16 additions & 1 deletion src/autoqasm/instructions/qubits.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@
from __future__ import annotations

import re
from collections.abc import Iterator
from collections.abc import Iterable, Iterator
from functools import singledispatch
from typing import Any

import oqpy.base
from openpulse.printer import dumps

from autoqasm import constants, errors, program
from autoqasm import types as aq_types


def _get_physical_qubit_indices(qids: list[str]) -> list[int]:
Expand Down Expand Up @@ -66,6 +67,20 @@ def global_qubit_register() -> GlobalQubitRegister:
return program.get_program_conversion_context().global_qubit_register


def _as_qubit_iterable(
qubits: aq_types.QubitIdentifierType | Iterable[aq_types.QubitIdentifierType] | None,
default: Iterable[aq_types.QubitIdentifierType] | None = None,
) -> Iterable[aq_types.QubitIdentifierType]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Since _as_qubit_iterable
can return a list or a GlobalQubitRegister, the return type is correct but could be more precise with Iterable[...] | GlobalQubitRegister

@rmshaffer rmshaffer May 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GlobalQubitRegister is already a Iterable[aq_types.QubitIdentifierType], and the caller should only care that what comes back is an Iterable[aq_types.QubitIdentifierType]. I think the extra type hint would be redundant here.

However in general it's confusing that GlobalQubitRegister is both an oqpy.Qubit and an Iterable of qubits - leaking a bit of oqpy abstractions through here, will look for a way to clean this up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

opened #110 to clean up the qubit-related types.

"""Normalize a qubit argument to an iterable. ``None`` maps to ``default`` (an empty
list if not provided); a single qubit identifier is wrapped in a list; iterables
(including :class:`GlobalQubitRegister`) pass through unchanged."""
if qubits is None:
qubits = default if default is not None else []
if aq_types.is_qubit_identifier_type(qubits) and not isinstance(qubits, GlobalQubitRegister):
return [qubits]
return qubits


@singledispatch
def _qubit(qid: Any) -> oqpy.Qubit:
"""Maps a given qubit representation to an oqpy qubit.
Expand Down
10 changes: 9 additions & 1 deletion src/autoqasm/program/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,16 +424,24 @@ def declare_global_qubit_register(self, size: int) -> None:
self.global_qubit_register.size = size
root_oqpy_program.declare(self.global_qubit_register, to_beginning=True)

def register_gate(self, gate_name: str) -> None:
def register_gate(self, gate_name: str, is_compiler_directive: bool = False) -> None:
"""Register a gate that is used in this program.

Args:
gate_name (str): The name of the gate being used.
is_compiler_directive (bool): Whether ``gate_name`` is a compiler directive
(e.g. ``barrier``) rather than a unitary gate. Compiler directives are exempt
from the verbatim-block native-gate restriction but are still validated at
build time against the target device's ``supportedOperations``. Default is False.

Raises:
errors.UnsupportedNativeGate: If the gate is being used inside a verbatim block
and the gate is not a native gate of the target device.
"""
if is_compiler_directive:
self._gates_used.add(gate_name)
return

if not self.in_verbatim_block:
self._gates_used.add(gate_name)
return
Expand Down
17 changes: 17 additions & 0 deletions test/unit_tests/autoqasm/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import autoqasm as aq
from autoqasm import errors
from autoqasm.instructions import cnot, h, measure, rx, x
from autoqasm.instructions.qubits import GlobalQubitRegister, _as_qubit_iterable
from autoqasm.simulator import McmSimulator
from braket.devices import LocalSimulator
from braket.tasks.local_quantum_task import LocalQuantumTask
Expand Down Expand Up @@ -1244,6 +1245,22 @@ def main():
assert main.build().to_ir() == expected_ir


def test_as_qubit_iterable_single_qubit_is_wrapped():
assert _as_qubit_iterable(0) == [0]
assert _as_qubit_iterable("$1") == ["$1"]


def test_as_qubit_iterable_iterable_passes_through():
assert _as_qubit_iterable([0, 1]) == [0, 1]
register = GlobalQubitRegister(size=3)
assert _as_qubit_iterable(register) is register


def test_as_qubit_iterable_none_uses_default():
assert _as_qubit_iterable(None) == []
assert _as_qubit_iterable(None, default=[5, 6]) == [5, 6]


def test_subroutine_call_with_kwargs():
"""Test that subroutine call works with keyword arguments"""

Expand Down
78 changes: 78 additions & 0 deletions test/unit_tests/autoqasm/test_barrier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

"""Tests for the ``barrier`` compiler directive."""

import pytest

import autoqasm as aq
from autoqasm.instructions import barrier, cnot, h


def test_barrier_on_qubits() -> None:
@aq.main
def program():
h(0)
barrier([0, 1])
cnot(0, 1)

expected_ir = """OPENQASM 3.0;
qubit[2] __qubits__;
h __qubits__[0];
barrier __qubits__[0], __qubits__[1];
cnot __qubits__[0], __qubits__[1];"""
assert program.build().to_ir() == expected_ir


def test_barrier_single_qubit() -> None:
@aq.main
def program():
h(0)
barrier(0)

expected_ir = """OPENQASM 3.0;
qubit[1] __qubits__;
h __qubits__[0];
barrier __qubits__[0];"""
assert program.build().to_ir() == expected_ir


def test_barrier_all_qubits() -> None:
@aq.main
def program():
h(0)
barrier()
cnot(0, 1)

expected_ir = """OPENQASM 3.0;
qubit[2] __qubits__;
h __qubits__[0];
barrier;
cnot __qubits__[0], __qubits__[1];"""
assert program.build().to_ir() == expected_ir


def test_barrier_disallowed_inside_gate_definition() -> None:
"""Barriers are compiler directives, not unitary gates, so they must not
appear inside ``@aq.gate`` bodies."""

@aq.gate
def bad_gate(q: aq.Qubit):
barrier(q)

@aq.main
def program():
bad_gate(0)

with pytest.raises(aq.errors.InvalidGateDefinition):
program.build()
61 changes: 60 additions & 1 deletion test/unit_tests/autoqasm/test_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import autoqasm as aq
from autoqasm import errors
from autoqasm.instructions import cnot, cphaseshift00, h, rx, x
from autoqasm.instructions import barrier, cnot, cphaseshift00, h, rx, x
from braket.aws import AwsDevice
from braket.device_schema import DeviceActionType
from braket.device_schema.simulators import GateModelSimulatorDeviceCapabilities
Expand Down Expand Up @@ -169,6 +169,65 @@ def my_program():
my_program.build(device=aws_device)


def test_barrier_inside_verbatim_allowed(aws_device: Mock) -> None:
"""Barriers are compiler directives, not native gates, and must be allowed
inside verbatim blocks regardless of the device's ``nativeGateSet``."""
aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["x", "barrier"]
aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"]
aws_device.properties.paradigm.nativeGateSet = ["x"]

@aq.main
def my_program():
with aq.verbatim():
x("$0")
barrier("$0")

expected_ir = """OPENQASM 3.0;
pragma braket verbatim
box {
x $0;
barrier $0;
}"""
assert my_program.build(device=aws_device).to_ir() == expected_ir


def test_barrier_outside_verbatim_allowed(aws_device: Mock) -> None:
"""Barriers are allowed outside verbatim blocks on devices that list
``barrier`` in their ``supportedOperations``."""
aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = [
"h",
"cnot",
"barrier",
]

@aq.main
def my_program():
h(0)
barrier([0, 1])
cnot(0, 1)

expected_ir = """OPENQASM 3.0;
qubit[2] __qubits__;
h __qubits__[0];
barrier __qubits__[0], __qubits__[1];
cnot __qubits__[0], __qubits__[1];"""
assert my_program.build(device=aws_device).to_ir() == expected_ir


def test_barrier_not_in_supported_operations(aws_device: Mock) -> None:
"""Devices that do not list ``barrier`` in ``supportedOperations`` should still
reject programs that use it."""
aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h"]

@aq.main
def my_program():
h(0)
barrier(0)

with pytest.raises(errors.UnsupportedGate):
my_program.build(device=aws_device)


def test_supported_native_gate_inside_gate_definition(aws_device: Mock) -> None:
aws_device.properties.action[DeviceActionType.OPENQASM].supportedOperations = ["h, x"]
aws_device.properties.action[DeviceActionType.OPENQASM].supportedPragmas = ["verbatim"]
Expand Down
Loading