Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: device-specific gate validation for autoqasm programs #695

Merged
merged 22 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7949484
Function wrapper code cleanup
rmshaffer Sep 7, 2023
b5efad3
Accept Device instead of only AwsDevice
rmshaffer Sep 7, 2023
5893392
Early return to simplify code structure
rmshaffer Sep 8, 2023
e326d46
Validate device supported operations
rmshaffer Sep 8, 2023
3336a76
Refactor aq.verbatim
rmshaffer Sep 8, 2023
2c7aca3
Basic native gate validation
rmshaffer Sep 8, 2023
3f592cd
Validate physical qubit connectivity in verbatim block
rmshaffer Sep 8, 2023
63f1630
Remove temporary change to flake8 args
rmshaffer Sep 8, 2023
da9f726
Allow custom gates in verbatim block
rmshaffer Sep 8, 2023
0c2429e
Add framework for native programming example notebook
rmshaffer Sep 8, 2023
5312afb
Add copyright
rmshaffer Sep 8, 2023
fadae11
Undo
rmshaffer Sep 8, 2023
faf5434
Remove new notebook from python-package action
rmshaffer Sep 11, 2023
6b062cf
Finish example notebook content
rmshaffer Sep 11, 2023
a4cb40f
Merge branch 'feature/autoqasm' into rmshaffer/autoqasm-device-valida…
rmshaffer Sep 11, 2023
dc85be7
Fix merge conflict
rmshaffer Sep 11, 2023
4d1d6ef
Use physical_qubit_to_braket_qubit instead of .strip("$")
rmshaffer Sep 11, 2023
6181c09
Make physical qubit string parsing method private
rmshaffer Sep 11, 2023
222c3cc
Explicitly disallow nested verbatim blocks
rmshaffer Sep 11, 2023
c69c9cd
Refactor box and verbatim context managers
rmshaffer Sep 11, 2023
b6714f1
Consolidate gate name normalization
rmshaffer Sep 12, 2023
b6891a0
Add tests for custom gate definitions
rmshaffer Sep 12, 2023
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 .github/workflows/check-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ jobs:
- name: Run code format checks
run: |
black --check . --extend-exclude=src/braket/experimental/autoqasm/autograph
flake8 --enable-extensions=BCS src/braket/experimental/autoqasm
flake8 --enable-extensions=BCS src
489 changes: 489 additions & 0 deletions examples/autoqasm/4_Native_programming.ipynb

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions examples/autoqasm/ionq_gates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import numpy as np

import braket.experimental.autoqasm as aq
from braket.experimental.autoqasm.instructions import gpi, gpi2, ms


@aq.gate
def h(q: aq.Qubit):
gpi2(q, np.pi / 2)
gpi(q, 0)


@aq.gate
def u(q: aq.Qubit, a: float, b: float, c: float):
gpi2(q, a)
gpi(q, b)
gpi2(q, c)


@aq.gate
def rx(q: aq.Qubit, theta: float):
u(q, np.pi / 2, theta / 2 + np.pi / 2, np.pi / 2)


@aq.gate
def ry(q: aq.Qubit, theta: float):
u(q, np.pi, theta / 2 + np.pi, np.pi)


@aq.gate
def cnot(q0: aq.Qubit, q1: aq.Qubit):
ry(q0, np.pi / 2)
ms(q0, q1, 0, 0, np.pi / 2)
rx(q0, -np.pi / 2)
rx(q1, -np.pi / 2)
ry(q0, -np.pi / 2)
37 changes: 19 additions & 18 deletions src/braket/experimental/autoqasm/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import braket.experimental.autoqasm.transpiler as aq_transpiler
import braket.experimental.autoqasm.types as aq_types
from braket.aws import AwsDevice
from braket.devices.device import Device
from braket.experimental.autoqasm import errors
from braket.experimental.autoqasm.autograph.core import converter
from braket.experimental.autoqasm.autograph.impl.api_core import (
Expand All @@ -41,7 +42,7 @@


def main(
*args, num_qubits: Optional[int] = None, device: Optional[Union[AwsDevice, str]] = None
*args, num_qubits: Optional[int] = None, device: Optional[Union[Device, str]] = None
) -> Callable[[Any], aq_program.Program]:
"""Decorator that converts a function into a callable that returns
a Program object containing the quantum program.
Expand All @@ -52,8 +53,8 @@ def main(
Args:
num_qubits (Optional[int]): Configuration to set the total number of qubits to declare in
the program.
device (Optional[Union[AwsDevice, str]]): Configuration to set the target device for the
program. Can be either an AwsDevice object or a valid Amazon Braket device ARN.
device (Optional[Union[Device, str]]): Configuration to set the target device for the
program. Can be either an Device object or a valid Amazon Braket device ARN.

Returns:
Callable[[Any], Program]: A callable which returns the converted
Expand All @@ -63,8 +64,8 @@ def main(
device = AwsDevice(device)

return _function_wrapper(
args,
_convert_main,
*args,
converter_callback=_convert_main,
converter_args={"user_config": aq_program.UserConfig(num_qubits=num_qubits, device=device)},
)

Expand All @@ -77,7 +78,7 @@ def subroutine(*args) -> Callable[[Any], aq_program.Program]:
Callable[[Any], Program]: A callable which returns the converted
quantum program when called.
"""
return _function_wrapper(args, _convert_subroutine)
return _function_wrapper(*args, converter_callback=_convert_subroutine)


def gate(*args) -> Callable[[Any], None]:
Expand All @@ -87,7 +88,7 @@ def gate(*args) -> Callable[[Any], None]:
Callable[[Any],]: A callable which can be used as a custom gate inside an
aq.function or inside another aq.gate.
"""
return _function_wrapper(args, _convert_gate)
return _function_wrapper(*args, converter_callback=_convert_gate)


def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], GateCalibration]:
Expand All @@ -104,20 +105,21 @@ def gate_calibration(*args, implements: Callable, **kwargs) -> Callable[[], Gate
Callable[[], GateCalibration]: A callable to be added to a main program using
`with_calibrations` method of the main program.
"""
converter_args = {"gate_function": implements, **kwargs}

return _function_wrapper(args, _convert_calibration, converter_args)
return _function_wrapper(
*args,
converter_callback=_convert_calibration,
converter_args={"gate_function": implements, **kwargs},
)


def _function_wrapper(
args: Tuple[Any],
*args: Tuple[Any],
converter_callback: Callable,
converter_args: Optional[Dict[str, Any]] = None,
) -> Callable[[Any], aq_program.Program]:
"""Wrapping and conversion logic around the user function `f`.

Args:
args (Tuple[Any]): The arguments to the decorated function.
converter_callback (Callable): The function converter, e.g., _convert_main.
converter_args (Optional[Dict[str, Any]]): Extra arguments for the function converter.

Expand All @@ -129,12 +131,11 @@ def _function_wrapper(
# This the case where a decorator is called with only keyword args, for example:
# @aq.main(num_qubits=4)
# def my_function():
# To make this work, here we simply return another wrapper function which expects
# a Callable as the first argument.
def _function_wrapper_with_params(*args) -> Callable[[Any], aq_program.Program]:
return _function_wrapper(args, converter_callback, converter_args=converter_args)

return _function_wrapper_with_params
# To make this work, here we simply return a partial application of this function
# which still expects a Callable as the first argument.
return functools.partial(
_function_wrapper, converter_callback=converter_callback, converter_args=converter_args
)

f = args[0]
if is_autograph_artifact(f):
Expand Down
16 changes: 16 additions & 0 deletions src/braket/experimental/autoqasm/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ class InvalidCalibrationDefinition(AutoQasmError):
"""Calibration definition does not meet the necessary requirements."""


class InvalidTargetQubit(AutoQasmError):
"""Target qubit is invalid in the current context."""


class UnsupportedGate(AutoQasmError):
"""Gate is not supported by the target device."""


class UnsupportedNativeGate(AutoQasmError):
"""Native gate is not supported by the target device."""


class VerbatimBlockNotAllowed(AutoQasmError):
"""Verbatim block is not supported by the target device."""


class UnknownQubitCountError(AutoQasmError):
"""Missing declaration for the number of qubits."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,11 @@
def _qubit_instruction(
name: str, qubits: List[QubitIdentifierType], *args: Any, is_unitary: bool = True
) -> None:
# If this is an instruction inside a gate definition, ensure that it only operates on
# qubits and angles which are passed as arguments to the gate definition.
program_conversion_context = aq_program.get_program_conversion_context()
program_conversion_context.validate_gate_targets(qubits, args)

# Add the instruction to the program.
program_conversion_context.register_gate(name)
program_mode = aq_program.ProgramMode.UNITARY if is_unitary else aq_program.ProgramMode.NONE
oqpy_program = program_conversion_context.get_oqpy_program(mode=program_mode)
oqpy_program.gate([_qubit(q) for q in qubits], name, *args)
Expand Down
24 changes: 23 additions & 1 deletion src/braket/experimental/autoqasm/instructions/qubits.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@

"""Utility functions that handle qubit construction and naming."""

import re
from functools import singledispatch
from typing import Any, Union
from typing import Any, List, Union

import oqpy.base
from openpulse.printer import dumps

from braket.circuits.qubit_set import QubitSet
from braket.experimental.autoqasm import constants, errors, program

QubitIdentifierType = Union[int, oqpy._ClassicalVar, oqpy.base.OQPyExpression, str, oqpy.Qubit]
Expand All @@ -37,6 +39,26 @@ def is_qubit_identifier_type(qubit: Any) -> bool:
return isinstance(qubit, QubitIdentifierType.__args__)


def physical_qubit_to_braket_qubit(qids: List[str]) -> QubitSet:
rmshaffer marked this conversation as resolved.
Show resolved Hide resolved
"""Convert a physical qubit label to a QubitSet.

Args:
qids (List[str]): Physical qubit labels.

Returns:
QubitSet: Represent physical qubits.
"""
braket_qubits = []
for qid in qids:
if not (isinstance(qid, str) and re.match(r"\$\d+", qid)):
raise ValueError(
f"invalid physical qubit label: '{qid}'. Physical qubit must be labeled as a string"
"with `$` followed by an integer. For example: `$1`."
)
braket_qubits.append(int(qid[1:]))
return QubitSet(braket_qubits)


def _global_qubit_register(qubit_idx_expr: Union[int, str]) -> str:
# TODO: We should index into a oqpy.QubitArray rather
# than manually generating the string to index into
Expand Down
2 changes: 1 addition & 1 deletion src/braket/experimental/autoqasm/program/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
for AutoQASM.
"""

from .pragmas import Verbatim as verbatim # noqa: F401
from .pragmas import verbatim # noqa: F401
from .program import ( # noqa: F401
GateArgs,
Program,
Expand Down
25 changes: 11 additions & 14 deletions src/braket/experimental/autoqasm/program/pragmas.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

@aq.main
def pragma_example() -> None:
with aq.Verbatim():
with aq.verbatim():
h(0)
cnot(0, 1)
x(0)
Expand All @@ -29,24 +29,21 @@ def pragma_example() -> None:
"""


import oqpy.base
import contextlib

from braket.experimental.autoqasm import program


class Verbatim:
@contextlib.contextmanager
def verbatim() -> None:
"""Context management protocol that, when used with a `with` statement, wraps the code block
in a verbatim box.
in a verbatim block.

The verbatim pragma around a code block specifies that operations are to be executed as
A verbatim block specifies that operations contained within the block are to be executed as
programmed without compilation or modification of any sort.
"""

def __enter__(self):
oqpy_program = program.get_program_conversion_context().get_oqpy_program()
self.box = oqpy.Box(oqpy_program)
oqpy_program.pragma("braket verbatim")
self.box.__enter__()

def __exit__(self, exc_type, exc, traceback):
return self.box.__exit__(exc_type, exc, traceback)
Raises:
errors.VerbatimBlockNotAllowed: If the target device does not support verbatim blocks.
"""
with program.get_program_conversion_context().verbatim_block():
yield
Loading