From 2bb1e743da965f6cc459b52d3cf580a53357741e Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 6 Dec 2023 10:07:39 -0800 Subject: [PATCH 001/347] update: adding a test to check for circular imports (#812) --- test/unit_tests/braket/test_imports.py | 48 ++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 test/unit_tests/braket/test_imports.py diff --git a/test/unit_tests/braket/test_imports.py b/test/unit_tests/braket/test_imports.py new file mode 100644 index 000000000..bd545d943 --- /dev/null +++ b/test/unit_tests/braket/test_imports.py @@ -0,0 +1,48 @@ +import importlib +import multiprocessing +import os +import pathlib + +import pytest + + +def test_for_import_cycles(): + # Note, because of all the multiprocessing in this test, when running 'tox', the process + # threads may be made to wait as other tests are running in parallel, making it seems like + # this test is much slower than it actually is. However, splitting the test into a + # parameterized version wasn't able to correctly detect some circular imports when running tox. + modules = get_modules_to_test() + processes = [] + multiprocessing.set_start_method("spawn") + for module in modules: + # We create a separate process to make sure the imports do not interfere with each-other. + process = multiprocessing.Process(target=import_module, args=(module,)) + processes.append(process) + process.start() + + for index, process in enumerate(processes): + process.join() + if process.exitcode != 0: + pytest.fail( + f"Unable to import '{modules[index]}'." + " If all other tests are passing, check for cyclical dependencies." + ) + + +def get_modules_to_test(): + curr_path = pathlib.Path(__file__).resolve() + while "test" in str(curr_path): + curr_path = curr_path.parent + curr_path = curr_path.joinpath("src") + curr_path_len = len(str(curr_path)) + len(os.sep) + modules = [] + for dir_, temp, files in os.walk(curr_path): + # Rather than testing every single python file we just test modules, for now. + if "__init__.py" in files: + braket_module = dir_[curr_path_len:].replace(os.sep, ".") + modules.append(braket_module) + return modules + + +def import_module(module): + importlib.import_module(module) From dedfb7206f301913d343520b9b2a6b4c7db62562 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 6 Dec 2023 11:09:52 -0800 Subject: [PATCH 002/347] infra: no unfrozen prefix when variable is not present (#810) --- .github/workflows/code-freeze.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml index 5aff0abc7..d19f5b48d 100644 --- a/.github/workflows/code-freeze.yml +++ b/.github/workflows/code-freeze.yml @@ -32,8 +32,18 @@ jobs: echo $BRANCH_NAME echo $PR_TITLE - if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]] && - [[ "$PR_TITLE" != fix:* && "$PR_TITLE" != *"[critical]"* ]]; then - echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with 'fix: ' and containing '[critical]'." - exit 1 + # if it's not a critical fix + if ! [[ "$PR_TITLE" == fix\(critical\):* ]]; then + # and there's an unfrozen prefix + if ! [[ -z $UNFROZEN_PREFIX ]]; then + # check if the branch matches unfrozen prefix + if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]]; then + echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with prefix 'fix(critical): '." + exit 1 + fi + # repo is fully frozen + else + echo "Error: You can only merge PRs titled with prefix 'fix(critical): '." + exit 1 + fi fi From 608a8dc3f72da42ee8913eb38fb9078e2121e50a Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 6 Dec 2023 12:42:20 -0800 Subject: [PATCH 003/347] feat: add str, repr and getitem to BasisState (#808) --- src/braket/circuits/basis_state.py | 12 ++++ .../braket/circuits/test_basis_state.py | 58 ++++++++++++++++++- 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index 814444e75..b6ce11bc8 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -33,6 +33,18 @@ def __iter__(self): def __eq__(self, other): return self.state == other.state + def __bool__(self): + return any(self.state) + + def __str__(self): + return self.as_string + + def __repr__(self): + return f'BasisState("{self.as_string}")' + + def __getitem__(self, item): + return BasisState(self.state[item]) + BasisStateInput = Union[int, list[int], str, BasisState] diff --git a/test/unit_tests/braket/circuits/test_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py index 023494fae..166e7c8fc 100644 --- a/test/unit_tests/braket/circuits/test_basis_state.py +++ b/test/unit_tests/braket/circuits/test_basis_state.py @@ -51,6 +51,58 @@ ), ) def test_as_props(basis_state_input, size, as_tuple, as_int, as_string): - assert BasisState(basis_state_input, size).as_tuple == as_tuple - assert BasisState(basis_state_input, size).as_int == as_int - assert BasisState(basis_state_input, size).as_string == as_string + basis_state = BasisState(basis_state_input, size) + assert basis_state.as_tuple == as_tuple + assert basis_state.as_int == as_int + assert basis_state.as_string == as_string == str(basis_state) + assert repr(basis_state) == f'BasisState("{as_string}")' + + +@pytest.mark.parametrize( + "basis_state_input, index, substate_input", + ( + ( + "1001", + slice(None), + "1001", + ), + ( + "1001", + 3, + "1", + ), + ( + "1010", + slice(None, None, 2), + "11", + ), + ( + "1010", + slice(1, None, 2), + "00", + ), + ( + "1010", + slice(None, -2), + "10", + ), + ( + "1010", + -1, + "0", + ), + ), +) +def test_indexing(basis_state_input, index, substate_input): + assert BasisState(basis_state_input)[index] == BasisState(substate_input) + + +def test_bool(): + assert all( + [ + BasisState("100"), + BasisState("111"), + BasisState("1"), + ] + ) + assert not BasisState("0") From 788b8b68a2835ee7ffb1d415c4dda0f4cf29c6bc Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Dec 2023 16:17:28 +0000 Subject: [PATCH 004/347] prepare release v1.64.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3514294..c8c50a7da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.64.0 (2023-12-07) + +### Features + + * add str, repr and getitem to BasisState + +### Bug Fixes and Other Changes + + * update: adding a test to check for circular imports + ## v1.63.0 (2023-12-05) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 70ca418cd..38e0376ee 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.63.1.dev0" +__version__ = "1.64.0" From 372cd53be84bbd27e1360b2ba15a5823404cd2da Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Dec 2023 16:17:28 +0000 Subject: [PATCH 005/347] update development version to v1.64.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 38e0376ee..156b961b8 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.0" +__version__ = "1.64.1.dev0" From d47c6fcfa16832fedc4fc8a95b7f3a6846e1a7fe Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:28:29 -0800 Subject: [PATCH 006/347] infra: allow tox to use logical processors (#824) Co-authored-by: Abe Coull Co-authored-by: Cody Wang --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 94f28ec7d..0a6ad7c49 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ test=pytest xfail_strict = true # https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html addopts = - --verbose -n auto --durations=0 --durations-min=1 + --verbose -n logical --durations=0 --durations-min=1 testpaths = test/unit_tests [isort] diff --git a/setup.py b/setup.py index f15777485..86ec939e3 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ "pytest", "pytest-cov", "pytest-rerunfailures", - "pytest-xdist", + "pytest-xdist[psutil]", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-apidoc", From bc126bc5062a26a4c9df04a3c2bd022866769e51 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Dec 2023 15:47:39 -0800 Subject: [PATCH 007/347] infra: ignore rsyncdir warning raised by rsync testing in pytest-cov (#817) --- setup.cfg | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/setup.cfg b/setup.cfg index 0a6ad7c49..3bad103a3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,11 @@ xfail_strict = true addopts = --verbose -n logical --durations=0 --durations-min=1 testpaths = test/unit_tests +filterwarnings= + # Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, + # but once a resolution has been adopted we can drop this "ignore". + # Ref: https://github.com/pytest-dev/pytest-cov/issues/557 + ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning [isort] line_length = 100 From 37bad90832b07a7ba81fbe88d1a2ae0213562847 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 13:18:31 -0700 Subject: [PATCH 008/347] infra: bump actions/setup-python from 4.7.1 to 5.0.0 (#833) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.7.1 to 5.0.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236...0a5c61591373683505ea898e09a3ea4f39ef2b9c) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index dbe9599c1..1795135e7 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index cfd395241..aca79d599 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e4d4e68c5..b12cde750 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 0c02a7561..56ba81bf4 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index f8e01fd72..2c30b8ec0 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # v4.7.1 + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 with: python-version: '3.x' - name: Install wheel From 7787d27123845eab2e5416fa1dd975ac8409a406 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:14:00 -0800 Subject: [PATCH 009/347] infra: Install only tox for python-package workflow (#791) --- .github/workflows/python-package.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 56ba81bf4..42dda2020 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -31,8 +31,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - pip install --upgrade pip - pip install -e .[test] + pip install tox - name: Run unit tests run: | tox -e unit-tests From 12f1387e0c715b398ed102a996fd436b77f297a5 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:57 -0500 Subject: [PATCH 010/347] fix: make filter more convenient (#718) * make filter more convenient * changes according to feedback * remove Union type hint --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Cody Wang --- src/braket/circuits/gate_calibrations.py | 25 +++++++++++-------- .../braket/circuits/test_gate_calibration.py | 19 +++++++++++--- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 6cbdd97d1..57013df4a 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -14,7 +14,7 @@ from __future__ import annotations from copy import deepcopy -from typing import Any, Optional +from typing import Any from braket.circuits.gate import Gate from braket.circuits.serialization import ( @@ -91,35 +91,40 @@ def __len__(self): return len(self._pulse_sequences) def filter( - self, gates: Optional[list[Gate]] = None, qubits: Optional[QubitSet] = None - ) -> Optional[GateCalibrations]: + self, + gates: list[Gate] | None = None, + qubits: QubitSet | list[QubitSet] | None = None, + ) -> GateCalibrations: """ Filters the data based on optional lists of gates and QubitSets. Args: - gates (Optional[list[Gate]]): An optional list of gates to filter on. - qubits (Optional[QubitSet]): An optional `QubitSet` to filter on. + gates (list[Gate] | None): An optional list of gates to filter on. + qubits (QubitSet | list[QubitSet] | None): An optional `QubitSet` or + list of `QubitSet` to filter on. Returns: - Optional[GateCalibrations]: A filtered GateCalibrations object. Otherwise, returns - none if no matches are found. + GateCalibrations: A filtered GateCalibrations object. """ # noqa: E501 keys = self.pulse_sequences.keys() + if isinstance(qubits, QubitSet): + qubits = [qubits] filtered_calibration_keys = [ tup for tup in keys - if (gates is None or tup[0] in gates) and (qubits is None or qubits.issubset(tup[1])) + if (gates is None or tup[0] in gates) + and (qubits is None or any(qset.issubset(tup[1]) for qset in qubits)) ] return GateCalibrations( {k: v for (k, v) in self.pulse_sequences.items() if k in filtered_calibration_keys}, ) - def to_ir(self, calibration_key: Optional[tuple[Gate, QubitSet]] = None) -> str: + def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: """ Returns the defcal representation for the `GateCalibrations` object. Args: - calibration_key (Optional[tuple[Gate, QubitSet]]): An optional key to get a specific defcal. + calibration_key (tuple[Gate, QubitSet] | None): An optional key to get a specific defcal. Default: None Returns: diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py index 31c2384db..c95ce74a3 100644 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -57,19 +57,30 @@ def test_gc_copy(pulse_sequence): def test_filter(pulse_sequence): - calibration_key = (Gate.Z(), QubitSet([0, 1])) - calibration_key_2 = (Gate.H(), QubitSet([0, 1])) + calibration_key = (Gate.Z(), QubitSet([0])) + calibration_key_2 = (Gate.H(), QubitSet([1])) + calibration_key_3 = (Gate.CZ(), QubitSet([0, 1])) calibration = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + { + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + calibration_key_3: pulse_sequence, + } ) expected_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) expected_calibration_2 = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} + {calibration_key: pulse_sequence, calibration_key_3: pulse_sequence} ) expected_calibration_3 = GateCalibrations({calibration_key_2: pulse_sequence}) + expected_calibration_4 = GateCalibrations({}) + expected_calibration_5 = calibration + expected_calibration_6 = GateCalibrations({calibration_key_3: pulse_sequence}) assert expected_calibration_1 == calibration.filter(gates=[Gate.Z()]) assert expected_calibration_2 == calibration.filter(qubits=QubitSet(0)) assert expected_calibration_3 == calibration.filter(gates=[Gate.H()], qubits=QubitSet(1)) + assert expected_calibration_4 == calibration.filter(gates=[Gate.Z()], qubits=QubitSet(1)) + assert expected_calibration_5 == calibration.filter(qubits=[QubitSet(0), QubitSet(1)]) + assert expected_calibration_6 == calibration.filter(qubits=QubitSet([0, 1])) def test_to_ir(pulse_sequence): From c1eb6644aab1dde1165d5d13e8a556eb04374bb5 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Dec 2023 16:16:20 +0000 Subject: [PATCH 011/347] prepare release v1.64.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8c50a7da..64c04fee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.64.1 (2023-12-12) + +### Bug Fixes and Other Changes + + * make filter more convenient + ## v1.64.0 (2023-12-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 156b961b8..1f45b0ddf 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.1.dev0" +__version__ = "1.64.1" From f44ab586d67a86464d3de7be282bcf878776a940 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 12 Dec 2023 16:16:20 +0000 Subject: [PATCH 012/347] update development version to v1.64.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1f45b0ddf..d63d59133 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.1" +__version__ = "1.64.2.dev0" From 228f0802716bc9cef5d886fd2aab3e6ec100929b Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Mon, 18 Dec 2023 10:19:06 -0800 Subject: [PATCH 013/347] fix: treating OpenQASM builtin types as constants (#829) --- src/braket/circuits/braket_program_context.py | 7 +++-- .../braket/circuits/test_circuit.py | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 9d8c69afe..19e2c986f 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -14,7 +14,7 @@ from typing import Optional, Union import numpy as np -from sympy import Expr +from sympy import Expr, Number from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary @@ -147,5 +147,8 @@ def handle_parameter_value( otherwise wraps the symbolic expression as a `FreeParameterExpression`. """ if isinstance(value, Expr): - return FreeParameterExpression(value) + evaluated_value = value.evalf() + if isinstance(evaluated_value, Number): + return evaluated_value + return FreeParameterExpression(evaluated_value) return value diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 5824967b8..71cff0de7 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1718,6 +1718,36 @@ def foo( inputs={}, ), ), + ( + Circuit().rx(0, np.pi), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(π) q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), + ( + Circuit().rx(0, 2 * np.pi), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(τ) q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), ], ) def test_from_ir(expected_circuit, ir): From b33191f4204388e33a0af52cae2dafb979aff6af Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Dec 2023 16:18:49 +0000 Subject: [PATCH 014/347] prepare release v1.64.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64c04fee2..3fc496e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.64.2 (2023-12-19) + +### Bug Fixes and Other Changes + + * treating OpenQASM builtin types as constants + ## v1.64.1 (2023-12-12) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d63d59133..d2cdebb9d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.2.dev0" +__version__ = "1.64.2" From d45c9c8429877ec843fa5886672a974189be51ff Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 19 Dec 2023 16:18:49 +0000 Subject: [PATCH 015/347] update development version to v1.64.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d2cdebb9d..a30db2260 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.2" +__version__ = "1.64.3.dev0" From 5d8d04c850459ac54633042ac41a8c0eebc3dc1c Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 20 Dec 2023 18:25:24 -0500 Subject: [PATCH 016/347] feat: add U and GPhase gates (#799) * Add U gate * modification according to feedback * fix linters * clean commented code * first version of gphase * handle drawing and control global phase * Adding a global phase attribute * add global phase to circuit unitary * first draft tests to check coverage * add more tests * add test with parametric global phase * add test for neg control qubit printing * clean up * simplify ctrl-gphase transform * feat: add str, repr and getitem to BasisState * add repr coverage * add index * add pop * fix phase target qubit * fix typing * add index and pop tests * fix code coverage * move unitary matrices * use a subindex in MomentKey * print global phase integration * fix docstrings * fix circuits zero total global phase * fix edge cases * fix to_unitary * temporary fix that checks classname * clean up test conditions * change logic according to feedback * update docstring * clean tests * update tests * replace control symbols * use box drawing characters * Revert "use box drawing characters" This reverts commit ccb81fa641fea45c7b3b8b2a597e7c4804e60b25. * Revert "replace control symbols" This reverts commit 4efb8bc53ba1d6bb42ba3df395a71ec324f7ea0d. * simplify all gphase case * change preprare_y_axis function name * create an helper function to compute the global phase * make control_basis_state more explicit * add comment and clean grouping * add test_only_neg_control_qubits * parametrize test_one_gate_with_global_phase * reformat * change to printing with fixed precision * fix docstring --------- Co-authored-by: Aaron Berdy --- src/braket/circuits/ascii_circuit_diagram.py | 148 +++++++++-- src/braket/circuits/braket_program_context.py | 11 +- src/braket/circuits/circuit.py | 13 +- src/braket/circuits/gate.py | 2 +- src/braket/circuits/gates.py | 237 +++++++++++++++++- src/braket/circuits/moments.py | 24 +- src/braket/circuits/translations.py | 2 + .../braket/circuits/test_angled_gate.py | 2 +- .../circuits/test_ascii_circuit_diagram.py | 84 +++++++ .../braket/circuits/test_circuit.py | 46 ++++ test/unit_tests/braket/circuits/test_gates.py | 100 +++++++- 11 files changed, 639 insertions(+), 30 deletions(-) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index daef01793..2c7024574 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -21,8 +21,10 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType from braket.circuits.noise import Noise from braket.circuits.result_type import ResultType +from braket.registers.qubit import Qubit from braket.registers.qubit_set import QubitSet @@ -44,23 +46,26 @@ def build_diagram(circuit: cir.Circuit) -> str: if not circuit.instructions: return "" + if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + return f"Global phase: {circuit.global_phase}" + circuit_qubits = circuit.qubits circuit_qubits.sort() - # Y Axis Column - y_axis_width = len(str(int(max(circuit_qubits)))) - y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) - for qubit in circuit_qubits: - y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) - y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) + y_axis_str, global_phase = AsciiCircuitDiagram._prepare_diagram_vars( + circuit, circuit_qubits + ) time_slices = circuit.moments.time_slices() column_strs = [] # Moment columns for time, instructions in time_slices.items(): + global_phase = AsciiCircuitDiagram._compute_moment_global_phase( + global_phase, instructions + ) moment_str = AsciiCircuitDiagram._ascii_diagram_column_set( - str(time), circuit_qubits, instructions + str(time), circuit_qubits, instructions, global_phase ) column_strs.append(moment_str) @@ -71,7 +76,7 @@ def build_diagram(circuit: cir.Circuit) -> str: if target_result_types: column_strs.append( AsciiCircuitDiagram._ascii_diagram_column_set( - "Result Types", circuit_qubits, target_result_types + "Result Types", circuit_qubits, target_result_types, global_phase ) ) @@ -84,6 +89,9 @@ def build_diagram(circuit: cir.Circuit) -> str: # Time on top and bottom lines.append(lines[0]) + if global_phase: + lines.append(f"\nGlobal phase: {global_phase}") + # Additional result types line on bottom if additional_result_types: lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") @@ -97,6 +105,49 @@ def build_diagram(circuit: cir.Circuit) -> str: return "\n".join(lines) + @staticmethod + def _prepare_diagram_vars( + circuit: cir.Circuit, circuit_qubits: QubitSet + ) -> tuple[str, float | None]: + # Y Axis Column + y_axis_width = len(str(int(max(circuit_qubits)))) + y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) + + global_phase = None + if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + y_axis_str += "{0:{width}} : |\n".format("GP", width=y_axis_width) + global_phase = 0 + + for qubit in circuit_qubits: + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) + + return y_axis_str, global_phase + + @staticmethod + def _compute_moment_global_phase( + global_phase: float | None, items: list[Instruction] + ) -> float | None: + """ + Compute the integrated phase at a certain moment. + + Args: + global_phase (float | None): The integrated phase up to the computed moment + items (list[Instruction]): list of instructions + + Returns: + float | None: The updated integrated phase. + """ + moment_phase = 0 + for item in items: + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + moment_phase += item.operator.angle + return global_phase + moment_phase if global_phase is not None else None + @staticmethod def _ascii_group_items( circuit_qubits: QubitSet, @@ -120,7 +171,15 @@ def _ascii_group_items( ): continue - if (isinstance(item, ResultType) and not item.target) or ( + # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range + # to an empty list and we just add it to the first group below. + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + qubit_range = QubitSet() + elif (isinstance(item, ResultType) and not item.target) or ( isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) ): qubit_range = circuit_qubits @@ -175,7 +234,10 @@ def _categorize_result_types( @staticmethod def _ascii_diagram_column_set( - col_title: str, circuit_qubits: QubitSet, items: list[Union[Instruction, ResultType]] + col_title: str, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None, ) -> str: """ Return a set of columns in the ASCII string diagram of the circuit for a list of items. @@ -184,6 +246,7 @@ def _ascii_diagram_column_set( col_title (str): title of column set circuit_qubits (QubitSet): qubits in circuit items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this set Returns: str: An ASCII string diagram for the column set. @@ -193,7 +256,7 @@ def _ascii_diagram_column_set( groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) column_strs = [ - AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1]) + AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1], global_phase) for grouping in groupings ] @@ -220,7 +283,9 @@ def _ascii_diagram_column_set( @staticmethod def _ascii_diagram_column( - circuit_qubits: QubitSet, items: list[Union[Instruction, ResultType]] + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None = None, ) -> str: """ Return a column in the ASCII string diagram of the circuit for a given list of items. @@ -228,9 +293,10 @@ def _ascii_diagram_column( Args: circuit_qubits (QubitSet): qubits in circuit items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column Returns: - str: An ASCII string diagram for the specified moment in time for a column. + str: an ASCII string diagram for the specified moment in time for a column. """ symbols = {qubit: "-" for qubit in circuit_qubits} margins = {qubit: " " for qubit in circuit_qubits} @@ -252,12 +318,26 @@ def _ascii_diagram_column( num_after = len(circuit_qubits) - 1 after = ["|"] * (num_after - 1) + ([marker] if num_after else []) ascii_symbols = [ascii_symbol] + after + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = QubitSet() + qubits = circuit_qubits + ascii_symbols = "-" * len(circuit_qubits) else: if isinstance(item.target, list): target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) else: target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) + map_control_qubit_states = AsciiCircuitDiagram._build_map_control_qubits( + item, control_qubits + ) + target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -288,20 +368,54 @@ def _ascii_diagram_column( else ascii_symbols[item_qubit_index] ) elif qubit in control_qubits: - symbols[qubit] = "C" + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" else: symbols[qubit] = "|" # Set the margin to be a connector if not on the first qubit - if qubit != min(target_and_control): + if target_and_control and qubit != min(target_and_control): margins[qubit] = "|" - symbols_width = max([len(symbol) for symbol in symbols.values()]) + output = AsciiCircuitDiagram._create_output(symbols, margins, circuit_qubits, global_phase) + return output + @staticmethod + def _create_output( + symbols: dict[Qubit, str], + margins: dict[Qubit, str], + qubits: QubitSet, + global_phase: float | None, + ) -> str: + symbols_width = max([len(symbol) for symbol in symbols.values()]) output = "" - for qubit in circuit_qubits: + + if global_phase is not None: + global_phase_str = ( + f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) + ) + symbols_width = max([symbols_width, len(global_phase_str)]) + output += "{0:{fill}{align}{width}}|\n".format( + global_phase_str, + fill=" ", + align="^", + width=symbols_width, + ) + + for qubit in qubits: output += "{0:{width}}\n".format(margins[qubit], width=symbols_width + 1) output += "{0:{fill}{align}{width}}\n".format( symbols[qubit], fill="-", align="<", width=symbols_width + 1 ) return output + + @staticmethod + def _build_map_control_qubits(item: Instruction, control_qubits: QubitSet) -> dict(Qubit, int): + control_state = getattr(item, "control_state", None) + if control_state is not None: + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + else: + map_control_qubit_states = {qubit: 1 for qubit in control_qubits} + + return map_control_qubit_states diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 19e2c986f..863513565 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -56,8 +56,15 @@ def is_builtin_gate(self, name: str) -> bool: user_defined_gate = self.is_user_defined_gate(name) return name in BRAKET_GATES and not user_defined_gate - def add_phase_instruction(self, target: tuple[int], phase_value: int) -> None: - raise NotImplementedError + def add_phase_instruction(self, target: tuple[int], phase_value: float) -> None: + """Add a global phase to the circuit. + + Args: + target (tuple[int]): Unused + phase_value (float): The phase value to be applied + """ + instruction = Instruction(BRAKET_GATES["gphase"](phase_value)) + self._circuit.add_instruction(instruction) def add_gate_instruction( self, gate_name: str, target: tuple[int], *params, ctrl_modifiers: list[int], power: float diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 76e986087..aeae8585d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -26,7 +26,7 @@ from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction -from braket.circuits.moments import Moments +from braket.circuits.moments import Moments, MomentType from braket.circuits.noise import Noise from braket.circuits.noise_helpers import ( apply_noise_to_gates, @@ -156,6 +156,17 @@ def depth(self) -> int: """int: Get the circuit depth.""" return self._moments.depth + @property + def global_phase(self) -> float: + """float: Get the global phase of the circuit.""" + return sum( + [ + instr.operator.angle + for moment, instr in self._moments.items() + if moment.moment_type == MomentType.GLOBAL_PHASE + ] + ) + @property def instructions(self) -> list[Instruction]: """Iterable[Instruction]: Get an `iterable` of instructions in the circuit.""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 3c59409e5..2183a2329 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -198,7 +198,7 @@ def _to_openqasm( return ( f"{inv_prefix}{power_prefix}{control_prefix}" - f"{self._qasm_name}{param_string} {', '.join(qubits)};" + f"{self._qasm_name}{param_string}{','.join([f' {qubit}' for qubit in qubits])};" ) @property diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 9a4214c37..e955c4bb0 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -30,7 +30,7 @@ angled_ascii_characters, get_angle, ) -from braket.circuits.basis_state import BasisStateInput +from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -215,6 +215,118 @@ def i( Gate.register_gate(I) +class GPhase(AngledGate): + r"""Global phase gate. + + Unitary matrix: + + .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + + Args: + angle (Union[FreeParameterExpression, float]): angle in radians. + """ + + def __init__(self, angle: Union[FreeParameterExpression, float]): + # Avoid parent constructor because _qubit_count must be zero + self._qubit_count = self.fixed_qubit_count() + self._ascii_symbols = [] + + if angle is None: + raise ValueError("angle must not be None") + if isinstance(angle, FreeParameterExpression): + self._parameters = [angle] + else: + self._parameters = [float(angle)] # explicit casting in case angle is e.g. np.float32 + + @property + def _qasm_name(self) -> str: + return "gphase" + + def adjoint(self) -> list[Gate]: + return [GPhase(-self.angle)] + + def to_matrix(self) -> np.ndarray: + return np.exp(1j * self.angle) * np.eye(1, dtype=complex) + + def bind_values(self, **kwargs) -> AngledGate: + return get_angle(self, **kwargs) + + @staticmethod + def fixed_qubit_count() -> int: + return 0 + + @staticmethod + @circuit.subroutine(register=True) + def gphase( + angle: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Instruction | Iterable[Instruction]: + r"""Global phase gate. + + If the gate is applied with control/negative control modifiers, it is translated in an + equivalent gate using the following definition: `phaseshift(λ) = ctrl @ gphase(λ)`. + The rightmost control qubit is used for the translation. If the polarity of the rightmost + control modifier is negative, the following identity is used: + `negctrl @ gphase(λ) q = x q; ctrl @ gphase(λ) q; x q`. + + Unitary matrix: + + .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + + Args: + angle (Union[FreeParameterExpression, float]): Phase in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Instruction | Iterable[Instruction]: GPhase instruction. + + Examples: + >>> circ = Circuit().gphase(0.45) + """ + if control is not None: + control_qubits = QubitSet(control) + + control_state = ( + control_state if control_state is not None else (1,) * len(control_qubits) + ) + control_basis_state = BasisState(control_state, len(control_qubits)) + + phaseshift_target = control_qubits[-1] + phaseshift_instruction = PhaseShift.phaseshift( + phaseshift_target, + angle, + control=control_qubits[:-1], + control_state=control_basis_state[:-1], + power=power, + ) + return ( + phaseshift_instruction + if control_basis_state[-1] + else [ + X.x(phaseshift_target), + phaseshift_instruction, + X.x(phaseshift_target), + ] + ) + + return Instruction(GPhase(angle), power=power) + + +Gate.register_gate(GPhase) + + class X(Gate): r"""Pauli-X gate. @@ -1287,6 +1399,129 @@ def phaseshift( Gate.register_gate(PhaseShift) +class U(TripleAngledGate): + r"""Generalized single-qubit rotation gate. + + Unitary matrix: + + .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} + \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ + e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + \end{bmatrix}. + + Args: + angle_1 (Union[FreeParameterExpression, float]): theta angle in radians. + angle_2 (Union[FreeParameterExpression, float]): phi angle in radians. + angle_3 (Union[FreeParameterExpression, float]): lambda angle in radians. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float], + ): + super().__init__( + angle_1=angle_1, + angle_2=angle_2, + angle_3=angle_3, + qubit_count=None, + ascii_symbols=[_multi_angled_ascii_characters("U", angle_1, angle_2, angle_3)], + ) + + @property + def _qasm_name(self) -> str: + return "U" + + def to_matrix(self) -> np.ndarray: + r"""Returns a matrix representation of this gate. + Returns: + ndarray: The matrix representation of this gate. + """ + _theta = self.angle_1 + _phi = self.angle_2 + _lambda = self.angle_3 + return np.array( + [ + [ + np.cos(_theta / 2), + -np.exp(1j * _lambda) * np.sin(_theta / 2), + ], + [ + np.exp(1j * _phi) * np.sin(_theta / 2), + np.exp(1j * (_phi + _lambda)) * np.cos(_theta / 2), + ], + ] + ) + + def adjoint(self) -> list[Gate]: + return [U(-self.angle_1, -self.angle_3, -self.angle_2)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs) -> TripleAngledGate: + return _get_angles(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def u( + target: QubitSetInput, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + angle_3: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: + r"""Generalized single-qubit rotation gate. + + Unitary matrix: + + .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} + \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ + e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + \end{bmatrix}. + + Args: + target (QubitSetInput): Target qubit(s) + angle_1 (Union[FreeParameterExpression, float]): theta angle in radians. + angle_2 (Union[FreeParameterExpression, float]): phi angle in radians. + angle_3 (Union[FreeParameterExpression, float]): lambda angle in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Iterable[Instruction]: U instruction. + + Examples: + >>> circ = Circuit().u(0, 0.15, 0.34, 0.52) + """ + return [ + Instruction( + U(angle_1, angle_2, angle_3), + target=qubit, + control=control, + control_state=control_state, + power=power, + ) + for qubit in QubitSet(target) + ] + + +Gate.register_gate(U) + + # Two qubit gates # diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 7cd8e0a9d..6e87db78d 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -19,6 +19,7 @@ from typing import Any, NamedTuple, Union from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction from braket.circuits.noise import Noise from braket.registers.qubit import Qubit @@ -42,6 +43,7 @@ class MomentType(str, Enum): INITIALIZATION_NOISE = "initialization_noise" READOUT_NOISE = "readout_noise" COMPILER_DIRECTIVE = "compiler_directive" + GLOBAL_PHASE = "global_phase" class MomentsKey(NamedTuple): @@ -59,6 +61,7 @@ class MomentsKey(NamedTuple): qubits: QubitSet moment_type: MomentType noise_index: int + subindex: int = 0 class Moments(Mapping[MomentsKey, Instruction]): @@ -106,6 +109,7 @@ def __init__(self, instructions: Iterable[Instruction] | None = None): self._qubits = QubitSet() self._depth = 0 self._time_all_qubits = -1 + self._number_gphase_in_current_moment = 0 self.add(instructions or []) @@ -181,6 +185,17 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._time_all_qubits = time elif isinstance(operator, Noise): self.add_noise(instruction) + elif isinstance(operator, Gate) and operator.name == "GPhase": + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._number_gphase_in_current_moment += 1 + key = MomentsKey( + time, + QubitSet([]), + MomentType.GLOBAL_PHASE, + 0, + self._number_gphase_in_current_moment, + ) + self._moments[key] = instruction else: qubit_range = instruction.target.union(instruction.control) time = self._update_qubit_times(qubit_range) @@ -188,14 +203,15 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._qubits.update(qubit_range) self._depth = max(self._depth, time + 1) + def _get_qubit_times(self, qubits: QubitSet) -> int: + return max([self._max_time_for_qubit(qubit) for qubit in qubits] + [self._time_all_qubits]) + def _update_qubit_times(self, qubits: QubitSet) -> int: - qubit_max_times = [self._max_time_for_qubit(qubit) for qubit in qubits] + [ - self._time_all_qubits - ] - time = max(qubit_max_times) + 1 + time = self._get_qubit_times(qubits) + 1 # Update time for all specified qubits for qubit in qubits: self._max_times[qubit] = time + self._number_gphase_in_current_moment = 0 return time def add_noise( diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index ba536594f..bbb194be3 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -31,6 +31,7 @@ from braket.ir.jaqcd.program_v1 import Results BRAKET_GATES = { + "gphase": braket_gates.GPhase, "i": braket_gates.I, "h": braket_gates.H, "x": braket_gates.X, @@ -55,6 +56,7 @@ "rx": braket_gates.Rx, "ry": braket_gates.Ry, "rz": braket_gates.Rz, + "U": braket_gates.U, "swap": braket_gates.Swap, "iswap": braket_gates.ISwap, "pswap": braket_gates.PSwap, diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index e76756e29..4e093e5b4 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -31,7 +31,7 @@ def test_is_operator(angled_gate): def test_angle_is_none(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="angle must not be None"): AngledGate(qubit_count=1, ascii_symbols=["foo"], angle=None) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 3f3268f83..916bfb050 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import numpy as np +import pytest from braket.circuits import ( AsciiCircuitDiagram, @@ -29,6 +30,10 @@ def test_empty_circuit(): assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" +def test_only_gphase_circuit(): + assert AsciiCircuitDiagram.build_diagram(Circuit().gphase(0.1)) == "Global phase: 0.1" + + def test_one_gate_one_qubit(): circ = Circuit().h(0) expected = ("T : |0|", " ", "q0 : -H-", "", "T : |0|") @@ -58,6 +63,35 @@ def test_one_gate_one_qubit_rotation_with_parameter(): _assert_correct_diagram(circ, expected) +@pytest.mark.parametrize("target", [0, 1]) +def test_one_gate_with_global_phase(target): + circ = Circuit().x(target=target).gphase(0.15) + expected = ( + "T : |0| 1 |", + "GP : |0|0.15|", + " ", + f"q{target} : -X------", + "", + "T : |0| 1 |", + "", + "Global phase: 0.15", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_zero_global_phase(): + circ = Circuit().gphase(-0.15).x(target=0).gphase(0.15) + expected = ( + "T : | 0 | 1 |", + "GP : |-0.15|0.00|", + " ", + "q0 : -X----------", + "", + "T : | 0 | 1 |", + ) + _assert_correct_diagram(circ, expected) + + def test_one_gate_one_qubit_rotation_with_unicode(): theta = FreeParameter("\u03B8") circ = Circuit().rx(angle=theta, target=0) @@ -74,6 +108,24 @@ def test_one_gate_one_qubit_rotation_with_unicode(): _assert_correct_diagram(circ, expected) +def test_one_gate_with_parametric_expression_global_phase_(): + theta = FreeParameter("\u03B8") + circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) + expected = ( + "T : |0| 1 | 2 |", + "GP : |0|2*θ|2*θ + 1.0|", + " ", + "q0 : -X-X-------------", + "", + "T : |0| 1 | 2 |", + "", + "Global phase: 2*θ + 1.0", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + def test_one_gate_one_qubit_rotation_with_parameter_assigned(): theta = FreeParameter("theta") circ = Circuit().rx(angle=theta, target=0) @@ -187,6 +239,38 @@ def test_connector_across_two_qubits(): _assert_correct_diagram(circ, expected) +def test_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=[0, 1]) + expected = ( + "T : |0|", + " ", + "q0 : -N-", + " | ", + "q1 : -C-", + " | ", + "q2 : -X-", + "", + "T : |0|", + ) + _assert_correct_diagram(circ, expected) + + +def test_only_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=0) + expected = ( + "T : |0|", + " ", + "q0 : -N-", + " | ", + "q1 : -N-", + " | ", + "q2 : -X-", + "", + "T : |0|", + ) + _assert_correct_diagram(circ, expected) + + def test_connector_across_three_qubits(): circ = Circuit().x(control=(3, 4), target=5).h(range(2, 6)) expected = ( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 71cff0de7..928cff757 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -1748,6 +1748,22 @@ def foo( inputs={}, ), ), + ( + Circuit().gphase(0.15).x(0), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "gphase(0.15);", + "x q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), ], ) def test_from_ir(expected_circuit, ir): @@ -2027,6 +2043,14 @@ def test_to_unitary_with_compiler_directives_returns_expected_unitary(): ) +def test_to_unitary_with_global_phase(): + circuit = Circuit().x(0) + circuit_unitary = np.array([[0, 1], [1, 0]]) + assert np.allclose(circuit.to_unitary(), circuit_unitary) + circuit = circuit.gphase(np.pi / 2) + assert np.allclose(circuit.to_unitary(), 1j * circuit_unitary) + + @pytest.mark.parametrize( "circuit,expected_unitary", [ @@ -2046,6 +2070,8 @@ def test_to_unitary_with_compiler_directives_returns_expected_unitary(): (Circuit().rx(0, 0.15), gates.Rx(0.15).to_matrix()), (Circuit().ry(0, 0.15), gates.Ry(0.15).to_matrix()), (Circuit().rz(0, 0.15), gates.Rz(0.15).to_matrix()), + (Circuit().u(0, 0.15, 0.16, 0.17), gates.U(0.15, 0.16, 0.17).to_matrix()), + (Circuit().gphase(0.15), gates.GPhase(0.15).to_matrix()), (Circuit().phaseshift(0, 0.15), gates.PhaseShift(0.15).to_matrix()), (Circuit().cnot(0, 1), gates.CNot().to_matrix()), (Circuit().cnot(0, 1).add_result_type(ResultType.StateVector()), gates.CNot().to_matrix()), @@ -3134,3 +3160,23 @@ def test_parametrized_pulse_circuit(user_defined_frame): def test_free_param_float_mix(): Circuit().ms(0, 1, 0.1, FreeParameter("theta")) + + +def test_circuit_with_global_phase(): + circuit = Circuit().gphase(0.15).x(0) + assert circuit.global_phase == 0.15 + + assert circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=OpenQASMSerializationProperties( + qubit_reference_type=QubitReferenceType.PHYSICAL + ), + ).source == "\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "gphase(0.15);", + "x $0;", + "b[0] = measure $0;", + ] + ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 05b98ade4..fc8fe7787 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -35,12 +35,21 @@ from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence +class NoTarget: + pass + + class TripleAngle: pass +class SingleNegControlModifier: + pass + + testdata = [ (Gate.H, "h", ir.H, [SingleTarget], {}), + (Gate.GPhase, "gphase", None, [NoTarget, Angle], {}), (Gate.I, "i", ir.I, [SingleTarget], {}), (Gate.X, "x", ir.X, [SingleTarget], {}), (Gate.Y, "y", ir.Y, [SingleTarget], {}), @@ -54,6 +63,7 @@ class TripleAngle: (Gate.Rx, "rx", ir.Rx, [SingleTarget, Angle], {}), (Gate.Ry, "ry", ir.Ry, [SingleTarget, Angle], {}), (Gate.Rz, "rz", ir.Rz, [SingleTarget, Angle], {}), + (Gate.U, "u", None, [SingleTarget, TripleAngle], {}), (Gate.CNot, "cnot", ir.CNot, [SingleTarget, SingleControl], {}), (Gate.CV, "cv", ir.CV, [SingleTarget, SingleControl], {}), (Gate.CCNot, "ccnot", ir.CCNot, [SingleTarget, DoubleControl], {}), @@ -118,9 +128,11 @@ class TripleAngle: ] parameterizable_gates = [ + Gate.GPhase, Gate.Rx, Gate.Ry, Gate.Rz, + Gate.U, Gate.PhaseShift, Gate.PSwap, Gate.XX, @@ -147,6 +159,10 @@ class TripleAngle: ] +def no_target_valid_input(**kwargs): + return {} + + def single_target_valid_input(**kwargs): return {"target": 2} @@ -171,6 +187,10 @@ def single_control_valid_input(**kwargs): return {"control": 0} +def single_neg_control_valid_input(**kwargs): + return {"control": [0], "control_state": [0]} + + def double_control_valid_ir_input(**kwargs): return {"controls": [0, 1]} @@ -193,11 +213,13 @@ def two_dimensional_matrix_valid_input(**kwargs): valid_ir_switcher = { + "NoTarget": no_target_valid_input, "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, "TripleAngle": triple_angle_valid_input, "SingleControl": single_control_valid_input, + "SingleNegControlModifier": single_neg_control_valid_input, "DoubleControl": double_control_valid_ir_input, "MultiTarget": multi_target_valid_input, "TwoDimensionalMatrix": two_dimensional_matrix_valid_ir_input, @@ -232,9 +254,13 @@ def create_valid_subroutine_input(irsubclasses, **kwargs): def create_valid_target_input(irsubclasses): input = {} qubit_set = [] + control_qubit_set = [] + control_state = None # based on the concept that control goes first in target input for subclass in irsubclasses: - if subclass == SingleTarget: + if subclass == NoTarget: + qubit_set.extend(list(no_target_valid_input().values())) + elif subclass == SingleTarget: qubit_set.extend(list(single_target_valid_input().values())) elif subclass == DoubleTarget: qubit_set.extend(list(double_target_valid_ir_input().values())) @@ -242,6 +268,9 @@ def create_valid_target_input(irsubclasses): qubit_set.extend(list(multi_target_valid_input().values())) elif subclass == SingleControl: qubit_set = list(single_control_valid_input().values()) + qubit_set + elif subclass == SingleNegControlModifier: + control_qubit_set = list(single_neg_control_valid_input()["control"]) + control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): @@ -249,6 +278,8 @@ def create_valid_target_input(irsubclasses): else: raise ValueError("Invalid subclass") input["target"] = QubitSet(qubit_set) + input["control"] = QubitSet(control_qubit_set) + input["control_state"] = control_state return input @@ -282,7 +313,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -434,6 +465,18 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), "rz(0.17) $4;", ), + ( + Gate.U(angle_1=0.17, angle_2=3.45, angle_3=5.21), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "U(0.17, 3.45, 5.21) q[4];", + ), + ( + Gate.U(angle_1=0.17, angle_2=3.45, angle_3=5.21), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "U(0.17, 3.45, 5.21) $4;", + ), ( Gate.XX(angle=0.17), [4, 5], @@ -859,9 +902,53 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar subroutine_input = {"target": multi_targets} if Angle in irsubclasses: subroutine_input.update(angle_valid_input()) + if TripleAngle in irsubclasses: + subroutine_input.update(triple_angle_valid_input()) assert subroutine(**subroutine_input) == Circuit(instruction_list) +@pytest.mark.parametrize( + "control, control_state, instruction_set", + [ + ( + 2, + None, + Instruction(**create_valid_instruction_input(Gate.PhaseShift, [SingleTarget, Angle])), + ), + ( + 2, + [0], + [ + Instruction(operator=Gate.X(), target=2), + Instruction( + **create_valid_instruction_input(Gate.PhaseShift, [SingleTarget, Angle]) + ), + Instruction(operator=Gate.X(), target=2), + ], + ), + ( + [0, 2], + [0, 1], + Instruction( + **create_valid_instruction_input( + Gate.PhaseShift, [SingleTarget, SingleNegControlModifier, Angle] + ) + ), + ), + ], +) +def test_control_gphase_subroutine(control, control_state, instruction_set): + subroutine = getattr(Circuit(), "gphase") + assert subroutine(angle=0.123, control=control, control_state=control_state) == Circuit( + instruction_set + ) + + +def test_angle_gphase_is_none(): + with pytest.raises(ValueError, match="angle must not be None"): + Gate.GPhase(angle=None) + + @pytest.mark.parametrize("testclass,subroutine_name,irclass,irsubclasses,kwargs", testdata) def test_gate_adjoint_expansion_correct(testclass, subroutine_name, irclass, irsubclasses, kwargs): gate = testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)) @@ -925,7 +1012,7 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): - triple_angled = gate.__name__ in ("MS",) + triple_angled = gate.__name__ in ("MS", "U") num_params = 3 if triple_angled else 1 thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] mapping = {f"theta_{i}": i for i in range(num_params)} @@ -1070,6 +1157,13 @@ def test_pulse_gate_to_matrix(): "10", "negctrl @ ctrl @ negctrl @ z q[1], q[2], q[3], q[0];", ), + ( + Gate.GPhase(0.3), + QubitSet([]), + QubitSet([1]), + "1", + "ctrl @ gphase(0.3) q[1];", + ), ), ) def test_gate_control(gate, target, control, control_state, expected_ir): From 1b35781505061f5a328d9fcda658d928890713eb Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Dec 2023 16:15:31 +0000 Subject: [PATCH 017/347] prepare release v1.65.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc496e21..37503c397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.65.0 (2023-12-21) + +### Features + + * add U and GPhase gates + ## v1.64.2 (2023-12-19) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a30db2260..d21a3b476 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.64.3.dev0" +__version__ = "1.65.0" From 788b198ddc66dc395b9761d1f92e38524d6eeed2 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Dec 2023 16:15:31 +0000 Subject: [PATCH 018/347] update development version to v1.65.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d21a3b476..ec7335e82 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.65.0" +__version__ = "1.65.1.dev0" From d5af538079d9cbb6f065da46e1e6731145e2a71b Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Fri, 22 Dec 2023 16:10:14 -0500 Subject: [PATCH 019/347] fix: validate out circuits that contain only non-zero-qubit gates (#842) * validate out all gphase circuits * update docstring * consider ctrl @ gphase * group error cases --- src/braket/circuits/circuit_helpers.py | 10 +++--- .../braket/circuits/test_circuit_helpers.py | 31 +++++++++++++++---- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 44e9bd571..f0e3f3144 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -23,13 +23,15 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: shots (int): shots to validate Raises: - ValueError: If circuit has no instructions; if no result types - specified for circuit and `shots=0`. See `braket.circuit.result_types`; + ValueError: If circuit has no instructions; if circuit has a non-gphase instruction; if no + result types specified for circuit and `shots=0`. See `braket.circuit.result_types`; if circuit has observables that cannot be simultaneously measured and `shots>0`; or, if `StateVector` or `Amplitude` are specified as result types when `shots>0`. """ - if not circuit.instructions: - raise ValueError("Circuit must have instructions to run on a device") + if not circuit.instructions or all( + not (inst.target or inst.control) for inst in circuit.instructions + ): + raise ValueError("Circuit must have at least one non-zero-qubit gate to run on a device") if not shots and not circuit.result_types: raise ValueError( "No result types specified for circuit and shots=0. See `braket.circuits.result_types`" diff --git a/test/unit_tests/braket/circuits/test_circuit_helpers.py b/test/unit_tests/braket/circuits/test_circuit_helpers.py index 56f7fd2ec..8960325cb 100644 --- a/test/unit_tests/braket/circuits/test_circuit_helpers.py +++ b/test/unit_tests/braket/circuits/test_circuit_helpers.py @@ -18,17 +18,32 @@ def test_validate_circuit_and_shots_no_instructions(): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Circuit must have at least one non-zero-qubit gate to run on a device" + ): validate_circuit_and_shots(Circuit(), 100) +def test_validate_circuit_and_shots_only_gphase(): + with pytest.raises( + ValueError, match="Circuit must have at least one non-zero-qubit gate to run on a device" + ): + validate_circuit_and_shots(Circuit().gphase(0.15), 100) + + +def test_validate_circuit_and_shots_ctrl_gphase(): + assert validate_circuit_and_shots(Circuit().gphase(0.15, control=[0]), 100) is None + + def test_validate_circuit_and_shots_0_no_instructions(): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="Circuit must have at least one non-zero-qubit gate to run on a device" + ): validate_circuit_and_shots(Circuit(), 0) def test_validate_circuit_and_shots_0_no_results(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="No result types specified for circuit and shots=0."): validate_circuit_and_shots(Circuit().h(0), 0) @@ -54,12 +69,16 @@ def test_validate_circuit_and_shots_100_results_mixed_result(): def test_validate_circuit_and_shots_100_result_state_vector(): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="StateVector or Amplitude cannot be specified when shots>0" + ): validate_circuit_and_shots(Circuit().h(0).state_vector(), 100) def test_validate_circuit_and_shots_100_result_amplitude(): - with pytest.raises(ValueError): + with pytest.raises( + ValueError, match="StateVector or Amplitude cannot be specified when shots>0" + ): validate_circuit_and_shots(Circuit().h(0).amplitude(state=["0"]), 100) @@ -74,7 +93,7 @@ def test_validate_circuit_and_shots_0_noncommuting(): def test_validate_circuit_and_shots_100_noncommuting(): - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Observables cannot be sampled simultaneously"): validate_circuit_and_shots( Circuit() .h(0) From b7ab26041804770edf65decde60a2e5a73d92dcb Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Dec 2023 16:16:49 +0000 Subject: [PATCH 020/347] prepare release v1.65.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37503c397..47d87d33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.65.1 (2023-12-25) + +### Bug Fixes and Other Changes + + * validate out circuits that contain only non-zero-qubit gates + ## v1.65.0 (2023-12-21) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ec7335e82..ad26cf05f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.65.1.dev0" +__version__ = "1.65.1" From 8018fcba7cb48724e3451c406176df8d6dcce3a6 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Dec 2023 16:16:49 +0000 Subject: [PATCH 021/347] update development version to v1.65.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ad26cf05f..278494287 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.65.1" +__version__ = "1.65.2.dev0" From 86a2f81bad3c1f3b2255fe619399b15cbd91ff68 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 10 Jan 2024 10:51:23 -0800 Subject: [PATCH 022/347] feat: update job name to use metadata (#853) --- src/braket/aws/aws_quantum_job.py | 2 +- test/integ_tests/test_create_quantum_job.py | 12 ++++++------ test/unit_tests/braket/aws/test_aws_quantum_job.py | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 1e929e857..cc6ce0ffa 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -268,7 +268,7 @@ def arn(self) -> str: @property def name(self) -> str: """str: The name of the quantum job.""" - return self._arn.partition("job/")[-1] + return self.metadata(use_cached_value=True).get("jobName") def state(self, use_cached_value: bool = False) -> str: """The state of the quantum hybrid job. diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 3b1b8ae95..77f60a266 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -52,15 +52,15 @@ def test_failed_quantum_job(aws_session, capsys): hyperparameters={"test_case": "failed"}, ) - job_name = job.name - pattern = f"^arn:aws:braket:{aws_session.region}:\\d12:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) + pattern = f"^arn:aws:braket:{aws_session.region}:\\d{{12}}:job/[a-z0-9-]+$" + assert re.match(pattern=pattern, string=job.arn) # Check job is in failed state. assert job.state() == "FAILED" # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. + job_name = job.name keys = aws_session.list_keys( bucket=f"amazon-braket-{aws_session.region}-{aws_session.account_id}", prefix=f"jobs/{job_name}", @@ -108,15 +108,15 @@ def test_completed_quantum_job(aws_session, capsys): hyperparameters={"test_case": "completed"}, ) - job_name = job.name - pattern = f"^arn:aws:braket:{aws_session.region}:\\d12:job/{job_name}$" - re.match(pattern=pattern, string=job.arn) + pattern = f"^arn:aws:braket:{aws_session.region}:\\d{{12}}:job/[a-z0-9-]+$" + assert re.match(pattern=pattern, string=job.arn) # check job is in completed state. assert job.state() == "COMPLETED" # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. + job_name = job.name s3_bucket = f"amazon-braket-{aws_session.region}-{aws_session.account_id}" keys = aws_session.list_keys( bucket=s3_bucket, diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 3a36d8e75..bd9316888 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -554,8 +554,9 @@ def test_arn(quantum_job_arn, aws_session): assert quantum_job.arn == quantum_job_arn -def test_name(quantum_job_arn, quantum_job_name, aws_session): +def test_name(quantum_job_arn, quantum_job_name, aws_session, generate_get_job_response): quantum_job = AwsQuantumJob(quantum_job_arn, aws_session) + aws_session.get_job.return_value = generate_get_job_response(jobName=quantum_job_name) assert quantum_job.name == quantum_job_name From 81a74cd27effb442a9129eef27dccd1273a65020 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Jan 2024 16:16:58 +0000 Subject: [PATCH 023/347] prepare release v1.66.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47d87d33a..1f8239b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.66.0 (2024-01-11) + +### Features + + * update job name to use metadata + ## v1.65.1 (2023-12-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 278494287..7b94d79aa 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.65.2.dev0" +__version__ = "1.66.0" From 4b5f690488f1ce405fddd800fc10d59f17dad427 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Jan 2024 16:16:58 +0000 Subject: [PATCH 024/347] update development version to v1.66.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7b94d79aa..71e2409ba 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.66.0" +__version__ = "1.66.1.dev0" From 7df9aa73912730adbdc7255fc7501942b007d982 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:35:25 -0800 Subject: [PATCH 025/347] feat: add queue position to the logs for tasks and jobs (#821) * feat: add logging for queue depth --- src/braket/aws/aws_quantum_job.py | 19 ++++-- src/braket/aws/aws_quantum_task.py | 14 +++++ src/braket/jobs/hybrid_job.py | 7 ++- src/braket/jobs/logs.py | 13 +++- .../braket/aws/test_aws_quantum_job.py | 61 +++++++++++++++++++ .../braket/aws/test_aws_quantum_task.py | 59 ++++++++++++++++++ 6 files changed, 166 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index cc6ce0ffa..562c61ba8 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -81,6 +81,7 @@ def create( aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, logger: Logger = getLogger(__name__), + quiet: bool = False, reservation_arn: str | None = None, ) -> AwsQuantumJob: """Creates a hybrid job by invoking the Braket CreateJob API. @@ -176,6 +177,9 @@ def create( while waiting for quantum task to be in a terminal state. Default is `getLogger(__name__)` + quiet (bool): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. + reservation_arn (str | None): the reservation window arn provided by Braket Direct to reserve exclusive usage for the device to run the hybrid job on. Default: None. @@ -210,7 +214,7 @@ def create( ) job_arn = aws_session.create_job(**create_job_kwargs) - job = AwsQuantumJob(job_arn, aws_session) + job = AwsQuantumJob(job_arn, aws_session, quiet) if wait_until_complete: print(f"Initializing Braket Job: {job_arn}") @@ -218,15 +222,18 @@ def create( return job - def __init__(self, arn: str, aws_session: AwsSession | None = None): + def __init__(self, arn: str, aws_session: AwsSession | None = None, quiet: bool = False): """ Args: arn (str): The ARN of the hybrid job. aws_session (AwsSession | None): The `AwsSession` for connecting to AWS services. Default is `None`, in which case an `AwsSession` object will be created with the region of the hybrid job. + quiet (bool): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. """ self._arn: str = arn + self._quiet = quiet if aws_session: if not self._is_valid_aws_session_region_for_job_arn(aws_session, arn): raise ValueError( @@ -371,10 +378,11 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: instance_count = self.metadata(use_cached_value=True)["instanceConfig"]["instanceCount"] has_streams = False color_wrap = logs.ColorWrap() + previous_state = self.state() while True: time.sleep(poll_interval_seconds) - + current_state = self.state() has_streams = logs.flush_log_streams( self._aws_session, log_group, @@ -384,14 +392,17 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: instance_count, has_streams, color_wrap, + [previous_state, current_state], + self.queue_position().queue_position if not self._quiet else None, ) + previous_state = current_state if log_state == AwsQuantumJob.LogState.COMPLETE: break if log_state == AwsQuantumJob.LogState.JOB_COMPLETE: log_state = AwsQuantumJob.LogState.COMPLETE - elif self.state() in AwsQuantumJob.TERMINAL_STATES: + elif current_state in AwsQuantumJob.TERMINAL_STATES: log_state = AwsQuantumJob.LogState.JOB_COMPLETE def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index c490a4190..0785c03c5 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -105,6 +105,7 @@ def create( tags: dict[str, str] | None = None, inputs: dict[str, float] | None = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None = None, + quiet: bool = False, reservation_arn: str | None = None, *args, **kwargs, @@ -152,6 +153,9 @@ def create( a `PulseSequence`. Default: None. + quiet (bool): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. + reservation_arn (str | None): The reservation ARN provided by Braket Direct to reserve exclusive usage for the device to run the quantum task on. Note: If you are creating tasks in a job that itself was created reservation ARN, @@ -215,6 +219,7 @@ def create( disable_qubit_rewiring, inputs, gate_definitions=gate_definitions, + quiet=quiet, *args, **kwargs, ) @@ -226,6 +231,7 @@ def __init__( poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), + quiet: bool = False, ): """ Args: @@ -238,6 +244,8 @@ def __init__( logger (Logger): Logger object with which to write logs, such as quantum task statuses while waiting for quantum task to be in a terminal state. Default is `getLogger(__name__)` + quiet (bool): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. Examples: >>> task = AwsQuantumTask(arn='task_arn') @@ -259,6 +267,7 @@ def __init__( self._poll_interval_seconds = poll_interval_seconds self._logger = logger + self._quiet = quiet self._metadata: dict[str, Any] = {} self._result: Union[ @@ -477,6 +486,11 @@ async def _wait_for_completion( while (time.time() - start_time) < self._poll_timeout_seconds: # Used cached metadata if cached status is terminal task_status = self._update_status_if_nonterminal() + if not self._quiet and task_status == "QUEUED": + queue = self.queue_position() + self._logger.debug( + f"Task is in {queue.queue_type} queue position: {queue.queue_position}" + ) self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: return self._download_result() diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index 707f18fd5..b8e1e58bf 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -63,6 +63,7 @@ def hybrid_job( aws_session: AwsSession | None = None, tags: dict[str, str] | None = None, logger: Logger = getLogger(__name__), + quiet: bool | None = None, reservation_arn: str | None = None, ) -> Callable: """Defines a hybrid job by decorating the entry point function. The job will be created @@ -71,7 +72,7 @@ def hybrid_job( The job created will be a `LocalQuantumJob` when `local` is set to `True`, otherwise an `AwsQuantumJob`. The following parameters will be ignored when running a job with `local` set to `True`: `wait_until_complete`, `instance_config`, `distribution`, - `copy_checkpoints_from_job`, `stopping_condition`, `tags`, and `logger`. + `copy_checkpoints_from_job`, `stopping_condition`, `tags`, `logger`, and `quiet`. Args: device (str | None): Device ARN of the QPU device that receives priority quantum @@ -153,6 +154,9 @@ def hybrid_job( logger (Logger): Logger object with which to write logs, such as task statuses while waiting for task to be in a terminal state. Default: `getLogger(__name__)` + quiet (bool | None): Sets the verbosity of the logger to low and does not report queue + position. Default is `False`. + reservation_arn (str | None): the reservation window arn provided by Braket Direct to reserve exclusive usage for the device to run the hybrid job on. Default: None. @@ -210,6 +214,7 @@ def job_wrapper(*args, **kwargs) -> Callable: "output_data_config": output_data_config, "aws_session": aws_session, "tags": tags, + "quiet": quiet, "reservation_arn": reservation_arn, } for key, value in optional_args.items(): diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index e0f54458d..734d51123 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -20,7 +20,7 @@ # Support for reading logs # ############################################################################## -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple from botocore.exceptions import ClientError @@ -155,7 +155,7 @@ def log_stream( yield ev -def flush_log_streams( +def flush_log_streams( # noqa C901 aws_session: AwsSession, log_group: str, stream_prefix: str, @@ -164,6 +164,8 @@ def flush_log_streams( stream_count: int, has_streams: bool, color_wrap: ColorWrap, + state: list[str], + queue_position: Optional[str] = None, ) -> bool: """Flushes log streams to stdout. @@ -183,6 +185,9 @@ def flush_log_streams( been found. This value is possibly updated and returned at the end of execution. color_wrap (ColorWrap): An instance of ColorWrap to potentially color-wrap print statements from different streams. + state (list[str]): The previous and current state of the job. + queue_position (Optional[str]): The current queue position. This is not passed in if the job + is ran with `quiet=True` Returns: bool: Returns 'True' if any streams have been flushed. @@ -225,6 +230,10 @@ def flush_log_streams( positions[stream_names[idx]] = Position(timestamp=ts, skip=count + 1) else: positions[stream_names[idx]] = Position(timestamp=event["timestamp"], skip=1) + elif queue_position is not None and state[1] == "QUEUED": + print(f"Job queue position: {queue_position}", end="\n", flush=True) + elif state[0] != state[1] and state[1] == "RUNNING" and queue_position is not None: + print("Running:", end="\n", flush=True) else: print(".", end="", flush=True) return has_streams diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index bd9316888..7f9dc1a84 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -93,6 +93,7 @@ def _get_job_response(**kwargs): "jobArn": "arn:aws:braket:us-west-2:875981177017:job/job-test-20210628140446", "jobName": "job-test-20210628140446", "outputDataConfig": {"s3Path": "s3://amazon-braket-jobs/job-path/data"}, + "queueInfo": {"position": "1", "queue": "JOBS_QUEUE"}, "roleArn": "arn:aws:iam::875981177017:role/AmazonBraketJobRole", "status": "RUNNING", "stoppingCondition": {"maxRuntimeInSeconds": 1200}, @@ -720,6 +721,14 @@ def test_logs( generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), generate_get_job_response(status="COMPLETED"), ) quantum_job._aws_session.describe_log_streams.side_effect = log_stream_responses @@ -740,6 +749,48 @@ def test_logs( ) +def test_logs_queue_progress( + quantum_job, + generate_get_job_response, + log_events_responses, + log_stream_responses, + capsys, +): + queue_info = {"queue": "JOBS_QUEUE", "position": "1"} + quantum_job._aws_session.get_job.side_effect = ( + generate_get_job_response(status="QUEUED", queue_info=queue_info), + generate_get_job_response(status="QUEUED", queue_info=queue_info), + generate_get_job_response(status="QUEUED", queue_info=queue_info), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + ) + quantum_job._aws_session.describe_log_streams.side_effect = log_stream_responses + quantum_job._aws_session.get_log_events.side_effect = log_events_responses + + quantum_job.logs(wait=True, poll_interval_seconds=0) + + captured = capsys.readouterr() + assert captured.out == "\n".join( + ( + f"Job queue position: {queue_info['position']}", + "Running:", + "", + "hi there #1", + "hi there #2", + "hi there #2a", + "hi there #3", + "", + ) + ) + + @patch.dict("os.environ", {"JPY_PARENT_PID": "True"}) def test_logs_multiple_instances( quantum_job, @@ -753,6 +804,15 @@ def test_logs_multiple_instances( generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="RUNNING"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), + generate_get_job_response(status="COMPLETED"), generate_get_job_response(status="COMPLETED"), ) log_stream_responses[-1]["logStreams"].append({"logStreamName": "stream-2"}) @@ -818,6 +878,7 @@ def get_log_events(log_group, log_stream, start_time, start_from_head, next_toke def test_logs_error(quantum_job, generate_get_job_response, capsys): quantum_job._aws_session.get_job.side_effect = ( + generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), generate_get_job_response(status="RUNNING"), generate_get_job_response(status="COMPLETED"), diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 656c37dcf..e96af57f5 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -83,6 +83,11 @@ def quantum_task(aws_session): return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) +@pytest.fixture +def quantum_task_quiet(aws_session): + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2, quiet=True) + + @pytest.fixture def circuit_task(aws_session): return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) @@ -243,6 +248,23 @@ def test_queue_position(quantum_task): ) +def test_queued_quiet(quantum_task_quiet): + state_1 = "QUEUED" + _mock_metadata(quantum_task_quiet._aws_session, state_1) + assert quantum_task_quiet.queue_position() == QuantumTaskQueueInfo( + queue_type=QueueType.NORMAL, queue_position="2", message=None + ) + + state_2 = "COMPLETED" + message = ( + f"'Task is in {state_2} status. AmazonBraket does not show queue position for this status.'" + ) + _mock_metadata(quantum_task_quiet._aws_session, state_2) + assert quantum_task_quiet.queue_position() == QuantumTaskQueueInfo( + queue_type=QueueType.NORMAL, queue_position=None, message=message + ) + + def test_state(quantum_task): state_1 = "RUNNING" _mock_metadata(quantum_task._aws_session, state_1) @@ -432,6 +454,43 @@ def set_result_from_callback(future): assert result_from_future == result +@pytest.mark.parametrize( + "status, result", + [ + ("COMPLETED", GateModelQuantumTaskResult.from_string(MockS3.MOCK_S3_RESULT_GATE_MODEL)), + ("FAILED", None), + ], +) +def test_async_result_queued(circuit_task, status, result): + def set_result_from_callback(future): + # Set the result_from_callback variable in the enclosing functions scope + nonlocal result_from_callback + result_from_callback = future.result() + + _mock_metadata(circuit_task._aws_session, "QUEUED") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) + + future = circuit_task.async_result() + + # test the different ways to get the result from async + + # via callback + result_from_callback = None + future.add_done_callback(set_result_from_callback) + + # via asyncio waiting for result + _mock_metadata(circuit_task._aws_session, status) + event_loop = asyncio.get_event_loop() + result_from_waiting = event_loop.run_until_complete(future) + + # via future.result(). Note that this would fail if the future is not complete. + result_from_future = future.result() + + assert result_from_callback == result + assert result_from_waiting == result + assert result_from_future == result + + def test_failed_task(quantum_task): _mock_metadata(quantum_task._aws_session, "FAILED") _mock_s3(quantum_task._aws_session, MockS3.MOCK_S3_RESULT_GATE_MODEL) From 354d369951cd590e6bd8b09bdc77461963c5c3ec Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 23 Jan 2024 16:18:08 +0000 Subject: [PATCH 026/347] prepare release v1.67.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f8239b03..f6f192141 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.67.0 (2024-01-23) + +### Features + + * add queue position to the logs for tasks and jobs + ## v1.66.0 (2024-01-11) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 71e2409ba..0a5eef4bc 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.66.1.dev0" +__version__ = "1.67.0" From 0dcf117f6d4fcd34fa3d051a6fbf811c4c0a1ee6 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 23 Jan 2024 16:18:08 +0000 Subject: [PATCH 027/347] update development version to v1.67.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0a5eef4bc..084dba82c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.67.0" +__version__ = "1.67.1.dev0" From df390e316b56835ee993870e5e4d6cdc67b8b1a0 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Wed, 24 Jan 2024 16:48:49 -0800 Subject: [PATCH 028/347] feat: update S3 locations for jobs (#857) S3 locations for jobs will now include a subdirectory with the epoch timestamp. --- src/braket/jobs/quantum_job_creation.py | 42 +++++++++++++++---- test/integ_tests/test_create_quantum_job.py | 40 ++++++++++++------ .../braket/jobs/test_quantum_job_creation.py | 18 ++++---- 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 657ed0829..9e18faeab 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -161,14 +161,15 @@ def prepare_quantum_job( _validate_params(param_datatype_map) aws_session = aws_session or AwsSession() device_config = DeviceConfig(device) - job_name = job_name or _generate_default_job_name(image_uri=image_uri) + timestamp = str(int(time.time() * 1000)) + job_name = job_name or _generate_default_job_name(image_uri=image_uri, timestamp=timestamp) role_arn = role_arn or os.getenv("BRAKET_JOBS_ROLE_ARN", aws_session.get_default_jobs_role()) hyperparameters = hyperparameters or {} hyperparameters = {str(key): str(value) for key, value in hyperparameters.items()} input_data = input_data or {} tags = tags or {} default_bucket = aws_session.default_bucket() - input_data_list = _process_input_data(input_data, job_name, aws_session) + input_data_list = _process_input_data(input_data, job_name, aws_session, timestamp) instance_config = instance_config or InstanceConfig() stopping_condition = stopping_condition or StoppingCondition() output_data_config = output_data_config or OutputDataConfig() @@ -177,6 +178,7 @@ def prepare_quantum_job( default_bucket, "jobs", job_name, + timestamp, "script", ) @@ -201,6 +203,7 @@ def prepare_quantum_job( default_bucket, "jobs", job_name, + timestamp, "data", ) if not checkpoint_config.s3Uri: @@ -208,6 +211,7 @@ def prepare_quantum_job( default_bucket, "jobs", job_name, + timestamp, "checkpoints", ) if copy_checkpoints_from_job: @@ -251,19 +255,22 @@ def prepare_quantum_job( return create_job_kwargs -def _generate_default_job_name(image_uri: str | None = None, func: Callable | None = None) -> str: +def _generate_default_job_name( + image_uri: str | None = None, func: Callable | None = None, timestamp: int | str | None = None +) -> str: """ Generate default job name using the image uri and entrypoint function. Args: image_uri (str | None): URI for the image container. func (Callable | None): The entry point function. + timestamp (int | str | None): Optional timestamp to use instead of generating one. Returns: str: Hybrid job name. """ max_length = 50 - timestamp = str(int(time.time() * 1000)) + timestamp = timestamp if timestamp is not None else str(int(time.time() * 1000)) if func: name = func.__name__.replace("_", "-") @@ -395,7 +402,10 @@ def _validate_params(dict_arr: dict[str, tuple[any, any]]) -> None: def _process_input_data( - input_data: str | dict | S3DataSourceConfig, job_name: str, aws_session: AwsSession + input_data: str | dict | S3DataSourceConfig, + job_name: str, + aws_session: AwsSession, + subdirectory: str, ) -> list[dict[str, Any]]: """ Convert input data into a list of dicts compatible with the Braket API. @@ -405,6 +415,7 @@ def _process_input_data( can be an S3DataSourceConfig or a str corresponding to a local prefix or S3 prefix. job_name (str): Hybrid job name. aws_session (AwsSession): AwsSession for possibly uploading local data. + subdirectory (str): Subdirectory within job name for S3 locations. Returns: list[dict[str, Any]]: A list of channel configs. @@ -413,12 +424,18 @@ def _process_input_data( input_data = {"input": input_data} for channel_name, data in input_data.items(): if not isinstance(data, S3DataSourceConfig): - input_data[channel_name] = _process_channel(data, job_name, aws_session, channel_name) + input_data[channel_name] = _process_channel( + data, job_name, aws_session, channel_name, subdirectory + ) return _convert_input_to_config(input_data) def _process_channel( - location: str, job_name: str, aws_session: AwsSession, channel_name: str + location: str, + job_name: str, + aws_session: AwsSession, + channel_name: str, + subdirectory: str, ) -> S3DataSourceConfig: """ Convert a location to an S3DataSourceConfig, uploading local data to S3, if necessary. @@ -427,6 +444,7 @@ def _process_channel( job_name (str): Hybrid job name. aws_session (AwsSession): AwsSession to be used for uploading local data. channel_name (str): Name of the channel. + subdirectory (str): Subdirectory within job name for S3 locations. Returns: S3DataSourceConfig: S3DataSourceConfig for the channel. @@ -435,10 +453,16 @@ def _process_channel( return S3DataSourceConfig(location) else: # local prefix "path/to/prefix" will be mapped to - # s3://bucket/jobs/job-name/data/input/prefix + # s3://bucket/jobs/job-name/subdirectory/data/input/prefix location_name = Path(location).name s3_prefix = AwsSession.construct_s3_uri( - aws_session.default_bucket(), "jobs", job_name, "data", channel_name, location_name + aws_session.default_bucket(), + "jobs", + job_name, + subdirectory, + "data", + channel_name, + location_name, ) aws_session.upload_local_data(location, s3_prefix) return S3DataSourceConfig(s3_prefix) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 77f60a266..02c16313b 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -61,11 +61,16 @@ def test_failed_quantum_job(aws_session, capsys): # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. job_name = job.name + s3_bucket = aws_session.default_bucket() + subdirectory = re.match( + rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", + job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], + ).group(1) keys = aws_session.list_keys( - bucket=f"amazon-braket-{aws_session.region}-{aws_session.account_id}", - prefix=f"jobs/{job_name}", + bucket=s3_bucket, + prefix=f"jobs/{job_name}/{subdirectory}/", ) - assert keys == [f"jobs/{job_name}/script/source.tar.gz"] + assert keys == [f"jobs/{job_name}/{subdirectory}/script/source.tar.gz"] # no results saved assert job.result() == {} @@ -117,24 +122,35 @@ def test_completed_quantum_job(aws_session, capsys): # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. job_name = job.name - s3_bucket = f"amazon-braket-{aws_session.region}-{aws_session.account_id}" + s3_bucket = aws_session.default_bucket() + subdirectory = re.match( + rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", + job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], + ).group(1) keys = aws_session.list_keys( bucket=s3_bucket, - prefix=f"jobs/{job_name}", + prefix=f"jobs/{job_name}/{subdirectory}/", ) for expected_key in [ - f"jobs/{job_name}/script/source.tar.gz", - f"jobs/{job_name}/data/output/model.tar.gz", - f"jobs/{job_name}/tasks/[^/]*/results.json", - f"jobs/{job_name}/checkpoints/{job_name}_plain_data.json", - f"jobs/{job_name}/checkpoints/{job_name}.json", + f"jobs/{job_name}/{subdirectory}/script/source.tar.gz", + f"jobs/{job_name}/{subdirectory}/data/output/model.tar.gz", + f"jobs/{job_name}/{subdirectory}/checkpoints/{job_name}_plain_data.json", + f"jobs/{job_name}/{subdirectory}/checkpoints/{job_name}.json", ]: assert any(re.match(expected_key, key) for key in keys) + # Check that tasks exist in the correct location + tasks_keys = aws_session.list_keys( + bucket=s3_bucket, + prefix=f"jobs/{job_name}/tasks/", + ) + expected_task_location = f"jobs/{job_name}/tasks/[^/]*/results.json" + assert any(re.match(expected_task_location, key) for key in tasks_keys) + # Check if checkpoint is uploaded in requested format. for s3_key, expected_data in [ ( - f"jobs/{job_name}/checkpoints/{job_name}_plain_data.json", + f"jobs/{job_name}/{subdirectory}/checkpoints/{job_name}_plain_data.json", { "braketSchemaHeader": { "name": "braket.jobs_data.persisted_job_data", @@ -145,7 +161,7 @@ def test_completed_quantum_job(aws_session, capsys): }, ), ( - f"jobs/{job_name}/checkpoints/{job_name}.json", + f"jobs/{job_name}/{subdirectory}/checkpoints/{job_name}.json", { "braketSchemaHeader": { "name": "braket.jobs_data.persisted_job_data", diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py index bef4fd643..8cd1fbca9 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -323,8 +323,9 @@ def _translate_creation_args(create_job_args): image_uri = create_job_args["image_uri"] job_name = create_job_args["job_name"] or _generate_default_job_name(image_uri) default_bucket = aws_session.default_bucket() + timestamp = str(int(time.time() * 1000)) code_location = create_job_args["code_location"] or AwsSession.construct_s3_uri( - default_bucket, "jobs", job_name, "script" + default_bucket, "jobs", job_name, timestamp, "script" ) role_arn = create_job_args["role_arn"] or aws_session.get_default_jobs_role() device = create_job_args["device"] @@ -340,11 +341,13 @@ def _translate_creation_args(create_job_args): } hyperparameters.update(distributed_hyperparams) output_data_config = create_job_args["output_data_config"] or OutputDataConfig( - s3Path=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, "data") + s3Path=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, timestamp, "data") ) stopping_condition = create_job_args["stopping_condition"] or StoppingCondition() checkpoint_config = create_job_args["checkpoint_config"] or CheckpointConfig( - s3Uri=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, "checkpoints") + s3Uri=AwsSession.construct_s3_uri( + default_bucket, "jobs", job_name, timestamp, "checkpoints" + ) ) entry_point = create_job_args["entry_point"] source_module = create_job_args["source_module"] @@ -365,7 +368,7 @@ def _translate_creation_args(create_job_args): "jobName": job_name, "roleArn": role_arn, "algorithmSpecification": algorithm_specification, - "inputDataConfig": _process_input_data(input_data, job_name, aws_session), + "inputDataConfig": _process_input_data(input_data, job_name, aws_session, timestamp), "instanceConfig": asdict(instance_config), "outputDataConfig": asdict(output_data_config, dict_factory=_exclude_nones_factory), "checkpointConfig": asdict(checkpoint_config), @@ -403,6 +406,7 @@ def test_generate_default_job_name(mock_time, image_uri): mock_time.return_value = datetime.datetime.now().timestamp() timestamp = str(int(time.time() * 1000)) assert _generate_default_job_name(image_uri) == f"braket-job{job_type}-{timestamp}" + assert _generate_default_job_name(image_uri, timestamp="ts") == f"braket-job{job_type}-ts" @pytest.mark.parametrize( @@ -602,7 +606,7 @@ def test_invalid_input_parameters(entry_point, aws_session): "channelName": "input", "dataSource": { "s3DataSource": { - "s3Uri": "s3://default-bucket-name/jobs/job-name/data/input/prefix", + "s3Uri": "s3://default-bucket-name/jobs/job-name/ts/data/input/prefix", }, }, } @@ -651,7 +655,7 @@ def test_invalid_input_parameters(entry_point, aws_session): "channelName": "local-input", "dataSource": { "s3DataSource": { - "s3Uri": "s3://default-bucket-name/jobs/job-name/" + "s3Uri": "s3://default-bucket-name/jobs/job-name/ts/" "data/local-input/prefix", }, }, @@ -678,4 +682,4 @@ def test_invalid_input_parameters(entry_point, aws_session): ) def test_process_input_data(aws_session, input_data, input_data_configs): job_name = "job-name" - assert _process_input_data(input_data, job_name, aws_session) == input_data_configs + assert _process_input_data(input_data, job_name, aws_session, "ts") == input_data_configs From 9a5f70d600cef9f4fd99de01eb69e3b5f9e72868 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Jan 2024 16:14:52 +0000 Subject: [PATCH 029/347] prepare release v1.68.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f192141..7bafd45c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.68.0 (2024-01-25) + +### Features + + * update S3 locations for jobs + ## v1.67.0 (2024-01-23) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 084dba82c..b52ccbb3f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.67.1.dev0" +__version__ = "1.68.0" From 9b0983703ef7f513ecf64400b81c26afa77b7e1d Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 25 Jan 2024 16:14:52 +0000 Subject: [PATCH 030/347] update development version to v1.68.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b52ccbb3f..051177529 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.0" +__version__ = "1.68.1.dev0" From cc28083dd5f73b02558887cd5598c350ad65aafd Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:33:55 -0800 Subject: [PATCH 031/347] infra: remove flake8-rst-docstring linter (#858) --- setup.py | 1 - tox.ini | 1 - 2 files changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 86ec939e3..d31f89f16 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,6 @@ "black", "botocore", "flake8<=5.0.4", - "flake8-rst-docstrings", "isort", "jsonschema==3.2.0", "pre-commit", diff --git a/tox.ini b/tox.ini index b77ac4525..467b9ab84 100644 --- a/tox.ini +++ b/tox.ini @@ -59,7 +59,6 @@ basepython = python3 skip_install = true deps = flake8 - flake8-rst-docstrings git+https://github.com/amazon-braket/amazon-braket-build-tools.git commands = flake8 --extend-exclude src {posargs} From 343f2be46fc76d0e973555ddc2a252b8fa142447 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 29 Jan 2024 13:36:54 -0800 Subject: [PATCH 032/347] fix: add force flag for import testing (#864) --- doc/conf.py | 1 + src/braket/aws/aws_device.py | 8 +++++--- src/braket/aws/aws_quantum_job.py | 8 +++++--- src/braket/aws/aws_quantum_task.py | 6 +++--- src/braket/circuits/gate.py | 8 +++++--- src/braket/pulse/pulse_sequence.py | 8 ++++---- src/braket/quantum_information/pauli_string.py | 8 +++++--- test/unit_tests/braket/aws/common_test_utils.py | 14 ++++++++------ test/unit_tests/braket/test_imports.py | 2 +- 9 files changed, 37 insertions(+), 26 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 23e9abdcf..2a8193e55 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,4 +1,5 @@ """Sphinx configuration.""" + import datetime import pkg_resources diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 503834dd8..14adacb3a 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -332,9 +332,11 @@ def _get_regional_device_session(self, session: AwsSession) -> AwsSession: self._populate_properties(region_session) return region_session except ClientError as e: - raise ValueError(f"'{self._arn}' not found") if e.response["Error"][ - "Code" - ] == "ResourceNotFoundException" else e + raise ( + ValueError(f"'{self._arn}' not found") + if e.response["Error"]["Code"] == "ResourceNotFoundException" + else e + ) def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: current_region = session.region diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 562c61ba8..31347311e 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -609,9 +609,11 @@ def _initialize_regional_device_session( aws_session.get_device(device) return aws_session except ClientError as e: - raise ValueError(f"'{device}' not found.") if e.response["Error"][ - "Code" - ] == "ResourceNotFoundException" else e + raise ( + ValueError(f"'{device}' not found.") + if e.response["Error"]["Code"] == "ResourceNotFoundException" + else e + ) @staticmethod def _initialize_non_regional_device_session( diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 0785c03c5..7b349310b 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -534,9 +534,9 @@ def _download_result( "has_reservation_arn": self._has_reservation_arn_from_metadata(current_metadata), } try: - task_event[ - "execution_duration" - ] = self._result.additional_metadata.simulatorMetadata.executionDuration + task_event["execution_duration"] = ( + self._result.additional_metadata.simulatorMetadata.executionDuration + ) except AttributeError: pass broadcast_event(_TaskCompletionEvent(**task_event)) diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 2183a2329..907c495ce 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -180,9 +180,11 @@ def _to_openqasm( for state, group in groupby(control_basis_state.as_tuple): modifier_name = "neg" * (not state) + "ctrl" control_modifiers += [ - f"{modifier_name}" - if (num_control := len(list(group))) == 1 - else f"{modifier_name}({num_control})" + ( + f"{modifier_name}" + if (num_control := len(list(group))) == 1 + else f"{modifier_name}({num_control})" + ) ] control_modifiers.append("") qubits = control_qubits + target_qubits diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index bb783c69b..98ea3c381 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -387,10 +387,10 @@ def _parse_from_calibration_schema( argument_value.update(QubitSet(int(argument["value"]))) instr_args["qubits_or_frames"] = argument_value elif argument["name"] in instr_args_keys: - instr_args[ - argument["name"] - ] = calibration_sequence._parse_arg_from_calibration_schema( - argument, waveforms, frames + instr_args[argument["name"]] = ( + calibration_sequence._parse_arg_from_calibration_schema( + argument, waveforms, frames + ) ) else: instr_args["qubits_or_frames"] = [] diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 8dca06625..f1ca55406 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -101,9 +101,11 @@ def weight_n_substrings(self, weight: int) -> tuple[PauliString, ...]: substrings = [] for indices in itertools.combinations(self._nontrivial, weight): factors = [ - self._nontrivial[qubit] - if qubit in set(indices).intersection(self._nontrivial) - else "I" + ( + self._nontrivial[qubit] + if qubit in set(indices).intersection(self._nontrivial) + else "I" + ) for qubit in range(self._qubit_count) ] substrings.append( diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 2975912f1..f1d6d96df 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -353,12 +353,14 @@ def _create_task_args_and_kwargs( create_kwargs = extra_kwargs or {} create_kwargs.update( { - "poll_timeout_seconds": poll_timeout_seconds - if poll_timeout_seconds is not None - else default_poll_timeout, - "poll_interval_seconds": poll_interval_seconds - if poll_interval_seconds is not None - else default_poll_interval, + "poll_timeout_seconds": ( + poll_timeout_seconds if poll_timeout_seconds is not None else default_poll_timeout + ), + "poll_interval_seconds": ( + poll_interval_seconds + if poll_interval_seconds is not None + else default_poll_interval + ), "inputs": inputs, "gate_definitions": gate_definitions, "reservation_arn": reservation_arn, diff --git a/test/unit_tests/braket/test_imports.py b/test/unit_tests/braket/test_imports.py index bd545d943..728c6311f 100644 --- a/test/unit_tests/braket/test_imports.py +++ b/test/unit_tests/braket/test_imports.py @@ -13,7 +13,7 @@ def test_for_import_cycles(): # parameterized version wasn't able to correctly detect some circular imports when running tox. modules = get_modules_to_test() processes = [] - multiprocessing.set_start_method("spawn") + multiprocessing.set_start_method("spawn", force=True) for module in modules: # We create a separate process to make sure the imports do not interfere with each-other. process = multiprocessing.Process(target=import_module, args=(module,)) From 6d1d7fc206364302ad652e323ead864dc6242344 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 29 Jan 2024 22:39:35 +0000 Subject: [PATCH 033/347] prepare release v1.68.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bafd45c3..6b587ce19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.68.1 (2024-01-29) + +### Bug Fixes and Other Changes + + * add force flag for import testing + ## v1.68.0 (2024-01-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 051177529..9dd7e0203 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.1.dev0" +__version__ = "1.68.1" From db0f1f26f400bff211442b3d291e9c173cf2da93 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 29 Jan 2024 22:39:35 +0000 Subject: [PATCH 034/347] update development version to v1.68.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9dd7e0203..36ac7a1eb 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.1" +__version__ = "1.68.2.dev0" From 6f20c328b91e17ff6f1268174e66756eb2d88c1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 Jan 2024 10:27:42 -0800 Subject: [PATCH 035/347] infra: bump codecov/codecov-action from 3.1.4 to 3.1.5 (#863) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.4 to 3.1.5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/eaaf4bedf32dbdc6b720b63067d99c4d77d6047d...4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0) --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 42dda2020..74b49db9e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,5 +36,5 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4 + uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 if: ${{ strategy.job-index }} == 0 From 058108c52c50f751080dc32de17c5fb4ed13d046 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 30 Jan 2024 13:55:23 -0800 Subject: [PATCH 036/347] fix: update batch circuit to limit repeat calls (#865) --- src/braket/aws/aws_quantum_task_batch.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 24ff15530..6c505e6ef 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -150,27 +150,36 @@ def _tasks_and_inputs( ]: inputs = inputs or {} + max_inputs_tasks = 1 single_task = isinstance( task_specifications, (Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation), ) single_input = isinstance(inputs, dict) + max_inputs_tasks = ( + max(max_inputs_tasks, len(task_specifications)) if not single_task else max_inputs_tasks + ) + max_inputs_tasks = ( + max(max_inputs_tasks, len(inputs)) if not single_input else max_inputs_tasks + ) + if not single_task and not single_input: if len(task_specifications) != len(inputs): raise ValueError( "Multiple inputs and task specifications must " "be equal in number." ) + if single_task: - task_specifications = repeat(task_specifications) + task_specifications = repeat(task_specifications, times=max_inputs_tasks) if single_input: - inputs = repeat(inputs) + inputs = repeat(inputs, times=max_inputs_tasks) tasks_and_inputs = zip(task_specifications, inputs) if single_task and single_input: - tasks_and_inputs = [next(tasks_and_inputs)] + tasks_and_inputs = list(tasks_and_inputs) tasks_and_inputs = list(tasks_and_inputs) From 5667ecc4c0d44505b29e8dea7cdf1a4a29ef9896 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:32:58 -0800 Subject: [PATCH 037/347] fix: update S3 uri regex for AWS sessions (#860) --- src/braket/aws/aws_session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 2ebc3c8d2..0534871a2 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -687,8 +687,8 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]: try: # Object URL e.g. https://my-bucket.s3.us-west-2.amazonaws.com/my/key # S3 URI e.g. s3://my-bucket/my/key - s3_uri_match = re.match("^https://([^./]+).[sS]3.[^/]+/(.*)$", s3_uri) or re.match( - "^[sS]3://([^./]+)/(.*)$", s3_uri + s3_uri_match = re.match(r"^https://([^./]+)\.[sS]3\.[^/]+/(.+)$", s3_uri) or re.match( + r"^[sS]3://([^./]+)/(.+)$", s3_uri ) assert s3_uri_match bucket, key = s3_uri_match.groups() From 8f797cc03bef5031079974fa1f0f68591ed2fa09 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 31 Jan 2024 16:14:52 +0000 Subject: [PATCH 038/347] prepare release v1.68.2 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b587ce19..6ac2a7325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.68.2 (2024-01-31) + +### Bug Fixes and Other Changes + + * update S3 uri regex for AWS sessions + * update batch circuit to limit repeat calls + ## v1.68.1 (2024-01-29) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 36ac7a1eb..b37c4d28d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.2.dev0" +__version__ = "1.68.2" From 458249d10eecd962c1df72ceff52e6e9d6f11228 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 31 Jan 2024 16:14:52 +0000 Subject: [PATCH 039/347] update development version to v1.68.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b37c4d28d..30917282e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.2" +__version__ = "1.68.3.dev0" From e07672e2426f9a1e03be9bf1eb636d8d1f4f06aa Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 2 Feb 2024 14:40:32 -0800 Subject: [PATCH 040/347] fix: Allow identities in PauliString observable (#867) --- .../quantum_information/pauli_string.py | 20 +++++++++++++++++-- .../quantum_information/test_pauli_string.py | 16 ++++++++------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index f1ca55406..1e4624e01 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -17,7 +17,7 @@ from typing import Optional, Union from braket.circuits.circuit import Circuit -from braket.circuits.observables import TensorProduct, X, Y, Z +from braket.circuits.observables import I, TensorProduct, X, Y, Z _IDENTITY = "I" _PAULI_X = "X" @@ -29,6 +29,7 @@ "Y": {"X": ["Z", -1j], "Z": ["X", 1j]}, "Z": {"X": ["Y", 1j], "Y": ["X", -1j]}, } +_ID_OBS = I() _PAULI_OBSERVABLES = {_PAULI_X: X(), _PAULI_Y: Y(), _PAULI_Z: Z()} _SIGN_MAP = {"+": 1, "-": -1} @@ -74,14 +75,29 @@ def qubit_count(self) -> int: """int: The number of qubits this Pauli string acts on.""" return self._qubit_count - def to_unsigned_observable(self) -> TensorProduct: + def to_unsigned_observable(self, include_trivial: bool = False) -> TensorProduct: """Returns the observable corresponding to the unsigned part of the Pauli string. For example, for a Pauli string -XYZ, the corresponding observable is X ⊗ Y ⊗ Z. + Args: + include_trivial (bool): Whether to include explicit identity factors in the observable. + Default: False. + Returns: TensorProduct: The tensor product of the unsigned factors in the Pauli string. """ + if include_trivial: + return TensorProduct( + [ + ( + _PAULI_OBSERVABLES[self._nontrivial[qubit]] + if qubit in self._nontrivial + else _ID_OBS + ) + for qubit in range(self._qubit_count) + ] + ) return TensorProduct( [_PAULI_OBSERVABLES[self._nontrivial[qubit]] for qubit in sorted(self._nontrivial)] ) diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string.py b/test/unit_tests/braket/quantum_information/test_pauli_string.py index 82e3b0698..c3959d305 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -20,7 +20,7 @@ from braket.circuits import gates from braket.circuits.circuit import Circuit -from braket.circuits.observables import X, Y, Z +from braket.circuits.observables import I, X, Y, Z from braket.quantum_information import PauliString ORDER = ["I", "X", "Y", "Z"] @@ -34,15 +34,16 @@ @pytest.mark.parametrize( - "pauli_string, string, phase, observable", + "pauli_string, string, phase, observable, obs_with_id", [ - ("+XZ", "+XZ", 1, X() @ Z()), - ("-ZXY", "-ZXY", -1, Z() @ X() @ Y()), - ("YIX", "+YIX", 1, Y() @ X()), - (PauliString("-ZYXI"), "-ZYXI", -1, Z() @ Y() @ X()), + ("+XZ", "+XZ", 1, X() @ Z(), X() @ Z()), + ("-ZXY", "-ZXY", -1, Z() @ X() @ Y(), Z() @ X() @ Y()), + ("YIX", "+YIX", 1, Y() @ X(), Y() @ I() @ X()), + (PauliString("-ZYXI"), "-ZYXI", -1, Z() @ Y() @ X(), Z() @ Y() @ X() @ I()), + ("IIXIIIYI", "+IIXIIIYI", 1, X() @ Y(), I() @ I() @ X() @ I() @ I() @ I() @ Y() @ I()), ], ) -def test_happy_case(pauli_string, string, phase, observable): +def test_happy_case(pauli_string, string, phase, observable, obs_with_id): instance = PauliString(pauli_string) assert str(instance) == string assert instance.phase == phase @@ -57,6 +58,7 @@ def test_happy_case(pauli_string, string, phase, observable): assert instance == PauliString(pauli_string) assert instance == PauliString(instance) assert instance.to_unsigned_observable() == observable + assert instance.to_unsigned_observable(include_trivial=True) == obs_with_id @pytest.mark.parametrize( From dfb5f2ebbd30f0638838a78204262fe668f8485a Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Feb 2024 16:15:47 +0000 Subject: [PATCH 041/347] prepare release v1.68.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac2a7325..5207697a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.68.3 (2024-02-05) + +### Bug Fixes and Other Changes + + * Allow identities in PauliString observable + ## v1.68.2 (2024-01-31) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 30917282e..aade395c9 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.3.dev0" +__version__ = "1.68.3" From f4f59551c76ce049a1b57ffa85db8a1e50206567 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 5 Feb 2024 16:15:47 +0000 Subject: [PATCH 042/347] update development version to v1.68.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index aade395c9..764a6422e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.3" +__version__ = "1.68.4.dev0" From 4e3e9d99b8c3b1a8a4f70c51ba54c9c276f31167 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:46:22 -0500 Subject: [PATCH 043/347] feat: update OQpy to version 0.3.5 (#697) * add duration type to FreeParameterExpression * consider FreeParameter as float * move to_ast to FreeParameterExprsesion * change back FPEIdentifier's parent to Identifier * clean up syntax * add precision about the expression type * add __repr__ to waveforms * do not simplify constants with defcals * add type validation * update oqpy to 0.3.2 * fix linters * increase test coverage * update to oqpy 0.3.3 * fix last merge commit * fix type hints * update to oqpy 0.3.4 * fix oqpy to 0.3.3 * remove FreeParameterExpressionIdentitifer * declare input parameters with pulse sequences * use machine-size types * create _InputVarSplitter * remove never visited branch * fix partial coverage * hacking test because the set order changes with python version * pass inputs with PulseSequence * pass empty dict to OpenQasmProgram * force FloatVar locally * add FreeDurationParameterExpression * simplify operation methods * use TYPE_CHECKING * move FreeDurationParameterExpression to pulse folder * remove TYPE_CHECKING * remove algebra rule for FreeDurationParameterExpression * accept integers as binding values * convert FreeParameter to OQpyExpression * fix coverage * clean tests * register freeparameters with delay * trigger GitHub actions * modify and rename _format_parameter_ast * update to oqpy 0.3.5 OQPy 0.3.5 converts now correctly ExpressionConvertible to duration * trigger GitHub actions * clean code * clean import * remove unnecessary test * remove IR transformer --------- Co-authored-by: Cody Wang Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Kshitij Chhabra Co-authored-by: Aaron Berdy --- setup.py | 2 +- src/braket/aws/aws_quantum_task.py | 7 +- src/braket/circuits/circuit.py | 17 ++- .../parametric/free_parameter_expression.py | 42 +++++-- src/braket/pulse/ast/free_parameters.py | 97 +++++++++------ src/braket/pulse/ast/qasm_parser.py | 25 +--- src/braket/pulse/pulse_sequence.py | 49 ++++---- src/braket/pulse/waveforms.py | 49 ++++---- .../braket/aws/test_aws_quantum_task.py | 2 +- .../braket/circuits/test_circuit.py | 34 +++-- .../braket/circuits/test_gate_calibration.py | 6 +- test/unit_tests/braket/circuits/test_gates.py | 4 +- .../test_free_parameter_expression.py | 1 + .../braket/pulse/test_pulse_sequence.py | 98 +++++++-------- .../unit_tests/braket/pulse/test_waveforms.py | 116 +++++++++++++----- 15 files changed, 318 insertions(+), 231 deletions(-) diff --git a/setup.py b/setup.py index d31f89f16..1e85974d5 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ install_requires=[ "amazon-braket-schemas>=1.19.1", "amazon-braket-default-simulator>=1.19.1", - "oqpy~=0.2.1", + "oqpy~=0.3.5", "setuptools", "backoff", "boltons", diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 7b349310b..34f22ae17 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -583,7 +583,12 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": OpenQASMProgram(source=pulse_sequence.to_ir()).json()}) + openqasm_program = OpenQASMProgram( + source=pulse_sequence.to_ir(), + inputs=inputs if inputs else {}, + ) + + create_task_kwargs.update({"action": openqasm_program.json()}) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index aeae8585d..ec44dec84 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -55,9 +55,10 @@ from braket.ir.jaqcd import Program as JaqcdProgram from braket.ir.openqasm import Program as OpenQasmProgram from braket.ir.openqasm.program_v1 import io_type -from braket.pulse import ArbitraryWaveform, Frame from braket.pulse.ast.qasm_parser import ast_to_qasm +from braket.pulse.frame import Frame from braket.pulse.pulse_sequence import PulseSequence, _validate_uniqueness +from braket.pulse.waveforms import Waveform from braket.registers.qubit import QubitInput from braket.registers.qubit_set import QubitSet, QubitSetInput @@ -1256,8 +1257,8 @@ def _create_openqasm_header( def _validate_gate_calbrations_uniqueness( self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], - frames: dict[Frame], - waveforms: dict[ArbitraryWaveform], + frames: dict[str, Frame], + waveforms: dict[str, Waveform], ) -> None: for key, calibration in gate_definitions.items(): for frame in calibration._frames.values(): @@ -1270,7 +1271,7 @@ def _validate_gate_calbrations_uniqueness( def _generate_frame_wf_defcal_declarations( self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] ) -> Optional[str]: - program = oqpy.Program(None) + program = oqpy.Program(None, simplify_constants=False) frames, waveforms = self._get_frames_waveforms_from_instrs(gate_definitions) @@ -1298,11 +1299,7 @@ def _generate_frame_wf_defcal_declarations( continue gate_name = gate._qasm_name - arguments = ( - [calibration._format_parameter_ast(value) for value in gate.parameters] - if isinstance(gate, Parameterizable) - else None - ) + arguments = gate.parameters if isinstance(gate, Parameterizable) else None with oqpy.defcal( program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments ): @@ -1315,7 +1312,7 @@ def _generate_frame_wf_defcal_declarations( def _get_frames_waveforms_from_instrs( self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] - ) -> tuple[dict[Frame], dict[ArbitraryWaveform]]: + ) -> tuple[dict[str, Frame], dict[str, Waveform]]: from braket.circuits.gates import PulseGate frames = {} diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index cd5fd7f89..98916fbf5 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -14,10 +14,14 @@ from __future__ import annotations import ast +import operator +from functools import reduce from numbers import Number from typing import Any, Union -from sympy import Expr, Float, Symbol, sympify +import sympy +from oqpy.base import OQPyExpression +from oqpy.classical_types import FloatVar class FreeParameterExpression: @@ -30,7 +34,7 @@ class FreeParameterExpression: present will NOT run. Values must be substituted prior to execution. """ - def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str]): + def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr, str]): """ Initializes a FreeParameterExpression. Best practice is to initialize using FreeParameters and Numbers. Not meant to be initialized directly. @@ -53,7 +57,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str] } if isinstance(expression, FreeParameterExpression): self._expression = expression.expression - elif isinstance(expression, (Number, Expr)): + elif isinstance(expression, (Number, sympy.Expr)): self._expression = expression elif isinstance(expression, str): self._expression = self._parse_string_expression(expression).expression @@ -61,7 +65,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, Expr, str] raise NotImplementedError @property - def expression(self) -> Union[Number, Expr]: + def expression(self) -> Union[Number, sympy.Expr]: """Gets the expression. Returns: Union[Number, Expr]: The expression for the FreeParameterExpression. @@ -70,7 +74,7 @@ def expression(self) -> Union[Number, Expr]: def subs( self, parameter_values: dict[str, Number] - ) -> Union[FreeParameterExpression, Number, Expr]: + ) -> Union[FreeParameterExpression, Number, sympy.Expr]: """ Similar to a substitution in Sympy. Parameters are swapped for corresponding values or expressions from the dictionary. @@ -103,7 +107,7 @@ def _eval_operation(self, node: Any) -> FreeParameterExpression: if isinstance(node, ast.Num): return FreeParameterExpression(node.n) elif isinstance(node, ast.Name): - return FreeParameterExpression(Symbol(node.id)) + return FreeParameterExpression(sympy.Symbol(node.id)) elif isinstance(node, ast.BinOp): if type(node.op) not in self._operations.keys(): raise ValueError(f"Unsupported binary operation: {type(node.op)}") @@ -158,7 +162,7 @@ def __neg__(self): def __eq__(self, other): if isinstance(other, FreeParameterExpression): - return sympify(self.expression).equals(sympify(other.expression)) + return sympy.sympify(self.expression).equals(sympy.sympify(other.expression)) return False def __repr__(self) -> str: @@ -170,6 +174,28 @@ def __repr__(self) -> str: """ return repr(self.expression) + def _to_oqpy_expression(self) -> OQPyExpression: + """Transforms into an OQPyExpression. + + Returns: + OQPyExpression: The AST node. + """ + ops = {sympy.Add: operator.add, sympy.Mul: operator.mul, sympy.Pow: operator.pow} + if isinstance(self.expression, tuple(ops)): + return reduce( + ops[type(self.expression)], + map( + lambda x: FreeParameterExpression(x)._to_oqpy_expression(), self.expression.args + ), + ) + elif isinstance(self.expression, sympy.Number): + return float(self.expression) + else: + fvar = FloatVar(name=self.expression.name, init_expression="input") + fvar.size = None + fvar.type.size = None + return fvar + def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: """Substitute a free parameter with the given kwargs, if any. @@ -182,7 +208,7 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: """ if isinstance(parameter, FreeParameterExpression): substituted = parameter.subs(kwargs) - if isinstance(substituted, Float): + if isinstance(substituted, sympy.Number): substituted = float(substituted) return substituted return parameter diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 1581ddd88..41c541da8 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -11,60 +11,85 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import operator from typing import Union from openpulse import ast -from openqasm3.ast import DurationLiteral from openqasm3.visitor import QASMTransformer - -from braket.parametric.free_parameter_expression import FreeParameterExpression - - -class _FreeParameterExpressionIdentifier(ast.Identifier): - """Dummy AST node with FreeParameterExpression instance attached""" - - def __init__(self, expression: FreeParameterExpression): - super().__init__(name=f"FreeParameterExpression({expression})") - self._expression = expression - - @property - def expression(self) -> FreeParameterExpression: - return self._expression +from oqpy.program import Program +from oqpy.timing import OQDurationLiteral class _FreeParameterTransformer(QASMTransformer): """Walk the AST and evaluate FreeParameterExpressions.""" - def __init__(self, param_values: dict[str, float]): + def __init__(self, param_values: dict[str, float], program: Program): self.param_values = param_values + self.program = program super().__init__() - def visit__FreeParameterExpressionIdentifier( + def visit_Identifier( self, identifier: ast.Identifier - ) -> Union[_FreeParameterExpressionIdentifier, ast.FloatLiteral]: - """Visit a FreeParameterExpressionIdentifier. + ) -> Union[ast.Identifier, ast.FloatLiteral]: + """Visit an Identifier. + + If the Identifier is used to hold a `FreeParameterExpression`, it will be simplified + using the given parameter values. + Args: identifier (Identifier): The identifier. Returns: - Union[_FreeParameterExpressionIdentifier, FloatLiteral]: The transformed expression. + Union[Identifier, FloatLiteral]: The transformed identifier. + """ + if identifier.name in self.param_values: + return ast.FloatLiteral(float(self.param_values[identifier.name])) + return identifier + + def visit_BinaryExpression( + self, node: ast.BinaryExpression + ) -> Union[ast.BinaryExpression, ast.FloatLiteral]: + """Visit a BinaryExpression. + + Visit the operands and simplify if they are literals. + + Args: + node (BinaryExpression): The node. + + Returns: + Union[BinaryExpression, FloatLiteral]: The transformed identifier. """ - new_value = identifier.expression.subs(self.param_values) - if isinstance(new_value, FreeParameterExpression): - return _FreeParameterExpressionIdentifier(new_value) - else: - return ast.FloatLiteral(new_value) - - def visit_DurationLiteral(self, duration_literal: DurationLiteral) -> DurationLiteral: - """Visit Duration Literal. - node.value, node.unit (node.unit.name, node.unit.value) - 1 + lhs = self.visit(node.lhs) + rhs = self.visit(node.rhs) + ops = { + ast.BinaryOperator["+"]: operator.add, + ast.BinaryOperator["*"]: operator.mul, + ast.BinaryOperator["**"]: operator.pow, + } + if isinstance(lhs, ast.FloatLiteral): + if isinstance(rhs, ast.FloatLiteral): + return ast.FloatLiteral(ops[node.op](lhs.value, rhs.value)) + elif isinstance(rhs, ast.DurationLiteral) and node.op == ast.BinaryOperator["*"]: + return OQDurationLiteral(lhs.value * rhs.value).to_ast(self.program) + return ast.BinaryExpression(op=node.op, lhs=lhs, rhs=rhs) + + def visit_UnaryExpression( + self, node: ast.UnaryExpression + ) -> Union[ast.UnaryExpression, ast.FloatLiteral]: + """Visit an UnaryExpression. + + Visit the operand and simplify if it is a literal. + Args: - duration_literal (DurationLiteral): The duration literal. + node (UnaryExpression): The node. + Returns: - DurationLiteral: The transformed duration literal. + Union[UnaryExpression, FloatLiteral]: The transformed identifier. """ - duration = duration_literal.value - if not isinstance(duration, FreeParameterExpression): - return duration_literal - return DurationLiteral(duration.subs(self.param_values), duration_literal.unit) + expression = self.visit(node.expression) + if ( + isinstance(expression, (ast.FloatLiteral, ast.DurationLiteral)) + and node.op == ast.UnaryOperator["-"] + ): + return type(expression)(-expression.value) + return ast.UnaryExpression(op=node.op, expression=node.expression) # pragma: no cover diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py index bd1b26e40..0146eca66 100644 --- a/src/braket/pulse/ast/qasm_parser.py +++ b/src/braket/pulse/ast/qasm_parser.py @@ -15,11 +15,8 @@ from openpulse import ast from openpulse.printer import Printer -from openqasm3.ast import DurationLiteral from openqasm3.printer import PrinterState -from braket.parametric.free_parameter_expression import FreeParameterExpression - class _PulsePrinter(Printer): """Walks the AST and prints it to an OpenQASM3 string.""" @@ -27,29 +24,13 @@ class _PulsePrinter(Printer): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - def visit__FreeParameterExpressionIdentifier( - self, node: ast.Identifier, context: PrinterState - ) -> None: - """Visit a FreeParameterExpressionIdentifier. + def visit_Identifier(self, node: ast.Identifier, context: PrinterState) -> None: + """Visit an Identifier. Args: node (ast.Identifier): The identifier. context (PrinterState): The printer state context. """ - self.stream.write(str(node.expression.expression)) - - def visit_DurationLiteral(self, node: DurationLiteral, context: PrinterState) -> None: - """Visit Duration Literal. - node.value, node.unit (node.unit.name, node.unit.value) - 1 - Args: - node (ast.DurationLiteral): The duration literal. - context (PrinterState): The printer state context. - """ - duration = node.value - if isinstance(duration, FreeParameterExpression): - self.stream.write(f"({duration.expression}){node.unit.name}") - else: - super().visit_DurationLiteral(node, context) + self.stream.write(str(node.name)) def visit_ClassicalDeclaration( self, node: ast.ClassicalDeclaration, context: PrinterState diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 98ea3c381..25e5c3b55 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -20,16 +20,12 @@ from openpulse import ast from oqpy import BitVar, PhysicalQubits, Program -from oqpy.timing import OQDurationLiteral from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import FreeParameterExpression from braket.parametric.parameterizable import Parameterizable from braket.pulse.ast.approximation_parser import _ApproximationParser -from braket.pulse.ast.free_parameters import ( - _FreeParameterExpressionIdentifier, - _FreeParameterTransformer, -) +from braket.pulse.ast.free_parameters import _FreeParameterTransformer from braket.pulse.ast.qasm_parser import ast_to_qasm from braket.pulse.ast.qasm_transformer import _IRQASMTransformer from braket.pulse.frame import Frame @@ -46,7 +42,7 @@ class PulseSequence: def __init__(self): self._capture_v0_count = 0 - self._program = Program() + self._program = Program(simplify_constants=False) self._frames = {} self._waveforms = {} self._free_parameters = set() @@ -87,7 +83,8 @@ def set_frequency( """ _validate_uniqueness(self._frames, frame) - self._program.set_frequency(frame=frame, freq=self._format_parameter_ast(frequency)) + self._register_free_parameters(frequency) + self._program.set_frequency(frame=frame, freq=frequency) self._frames[frame.id] = frame return self @@ -106,7 +103,8 @@ def shift_frequency( PulseSequence: self, with the instruction added. """ _validate_uniqueness(self._frames, frame) - self._program.shift_frequency(frame=frame, freq=self._format_parameter_ast(frequency)) + self._register_free_parameters(frequency) + self._program.shift_frequency(frame=frame, freq=frequency) self._frames[frame.id] = frame return self @@ -125,7 +123,8 @@ def set_phase( PulseSequence: self, with the instruction added. """ _validate_uniqueness(self._frames, frame) - self._program.set_phase(frame=frame, phase=self._format_parameter_ast(phase)) + self._register_free_parameters(phase) + self._program.set_phase(frame=frame, phase=phase) self._frames[frame.id] = frame return self @@ -144,7 +143,8 @@ def shift_phase( PulseSequence: self, with the instruction added. """ _validate_uniqueness(self._frames, frame) - self._program.shift_phase(frame=frame, phase=self._format_parameter_ast(phase)) + self._register_free_parameters(phase) + self._program.shift_phase(frame=frame, phase=phase) self._frames[frame.id] = frame return self @@ -163,7 +163,8 @@ def set_scale( PulseSequence: self, with the instruction added. """ _validate_uniqueness(self._frames, frame) - self._program.set_scale(frame=frame, scale=self._format_parameter_ast(scale)) + self._register_free_parameters(scale) + self._program.set_scale(frame=frame, scale=scale) self._frames[frame.id] = frame return self @@ -183,10 +184,7 @@ def delay( Returns: PulseSequence: self, with the instruction added. """ - if isinstance(duration, FreeParameterExpression): - for p in duration.expression.free_symbols: - self._free_parameters.add(FreeParameter(p.name)) - duration = OQDurationLiteral(duration) + self._register_free_parameters(duration) if not isinstance(qubits_or_frames, QubitSet): if not isinstance(qubits_or_frames, list): qubits_or_frames = [qubits_or_frames] @@ -234,12 +232,10 @@ def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: """ _validate_uniqueness(self._frames, frame) _validate_uniqueness(self._waveforms, waveform) - self._program.play(frame=frame, waveform=waveform) if isinstance(waveform, Parameterizable): for param in waveform.parameters: - if isinstance(param, FreeParameterExpression): - for p in param.expression.free_symbols: - self._free_parameters.add(FreeParameter(p.name)) + self._register_free_parameters(param) + self._program.play(frame=frame, waveform=waveform) self._frames[frame.id] = frame self._waveforms[waveform.id] = waveform return self @@ -276,11 +272,13 @@ def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequ """ program = deepcopy(self._program) tree: ast.Program = program.to_ast(include_externs=False, ignore_needs_declaration=True) - new_tree: ast.Program = _FreeParameterTransformer(param_values).visit(tree) + new_tree: ast.Program = _FreeParameterTransformer(param_values, program).visit(tree) - new_program = Program() + new_program = Program(simplify_constants=False) new_program.declared_vars = program.declared_vars new_program.undeclared_vars = program.undeclared_vars + for param_name in param_values: + new_program.undeclared_vars.pop(param_name, None) for x in new_tree.statements: new_program._add_statement(x) @@ -324,14 +322,13 @@ def to_ir(self) -> str: tree = program.to_ast(encal=True, include_externs=False) return ast_to_qasm(tree) - def _format_parameter_ast( - self, parameter: Union[float, FreeParameterExpression] - ) -> Union[float, _FreeParameterExpressionIdentifier]: + def _register_free_parameters( + self, + parameter: Union[float, FreeParameterExpression], + ) -> None: if isinstance(parameter, FreeParameterExpression): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) - return _FreeParameterExpressionIdentifier(parameter) - return parameter def _parse_arg_from_calibration_schema( self, argument: dict, waveforms: dict[Waveform], frames: dict[Frame] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index dbf89e146..971321a71 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -21,7 +21,6 @@ import numpy as np from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 from oqpy.base import OQPyExpression -from oqpy.timing import OQDurationLiteral from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import ( @@ -29,7 +28,6 @@ subs_if_free_parameter, ) from braket.parametric.parameterizable import Parameterizable -from braket.pulse.ast.free_parameters import _FreeParameterExpressionIdentifier class Waveform(ABC): @@ -85,6 +83,9 @@ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): self.amplitudes = list(amplitudes) self.id = id or _make_identifier_name() + def __repr__(self) -> str: + return f"ArbitraryWaveform('id': {self.id}, 'amplitudes': {self.amplitudes})" + def __eq__(self, other): return isinstance(other, ArbitraryWaveform) and (self.amplitudes, self.id) == ( other.amplitudes, @@ -133,6 +134,9 @@ def __init__( self.iq = iq self.id = id or _make_identifier_name() + def __repr__(self) -> str: + return f"ConstantWaveform('id': {self.id}, 'length': {self.length}, 'iq': {self.iq})" + @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter @@ -169,7 +173,7 @@ def _to_oqpy_expression(self) -> OQPyExpression: "constant", [("length", duration), ("iq", complex128)] ) return WaveformVar( - init_expression=constant_generator(_map_to_oqpy_type(self.length, True), self.iq), + init_expression=constant_generator(self.length, self.iq), name=self.id, ) @@ -238,6 +242,13 @@ def __init__( self.zero_at_edges = zero_at_edges self.id = id or _make_identifier_name() + def __repr__(self) -> str: + return ( + f"DragGaussianWaveform('id': {self.id}, 'length': {self.length}, " + f"'sigma': {self.sigma}, 'beta': {self.beta}, 'amplitude': {self.amplitude}, " + f"'zero_at_edges': {self.zero_at_edges})" + ) + @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter @@ -288,10 +299,10 @@ def _to_oqpy_expression(self) -> OQPyExpression: ) return WaveformVar( init_expression=drag_gaussian_generator( - _map_to_oqpy_type(self.length, True), - _map_to_oqpy_type(self.sigma, True), - _map_to_oqpy_type(self.beta), - _map_to_oqpy_type(self.amplitude), + self.length, + self.sigma, + self.beta, + self.amplitude, self.zero_at_edges, ), name=self.id, @@ -362,6 +373,12 @@ def __init__( self.zero_at_edges = zero_at_edges self.id = id or _make_identifier_name() + def __repr__(self) -> str: + return ( + f"GaussianWaveform('id': {self.id}, 'length': {self.length}, 'sigma': {self.sigma}, " + f"'amplitude': {self.amplitude}, 'zero_at_edges': {self.zero_at_edges})" + ) + @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter @@ -409,9 +426,9 @@ def _to_oqpy_expression(self) -> OQPyExpression: ) return WaveformVar( init_expression=gaussian_generator( - _map_to_oqpy_type(self.length, True), - _map_to_oqpy_type(self.sigma, True), - _map_to_oqpy_type(self.amplitude), + self.length, + self.sigma, + self.amplitude, self.zero_at_edges, ), name=self.id, @@ -452,18 +469,6 @@ def _make_identifier_name() -> str: return "".join([random.choice(string.ascii_letters) for _ in range(10)]) -def _map_to_oqpy_type( - parameter: Union[FreeParameterExpression, float], is_duration_type: bool = False -) -> Union[_FreeParameterExpressionIdentifier, OQPyExpression]: - if isinstance(parameter, FreeParameterExpression): - return ( - OQDurationLiteral(parameter) - if is_duration_type - else _FreeParameterExpressionIdentifier(parameter) - ) - return parameter - - def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: waveform_names = { "arbitrary": ArbitraryWaveform._from_calibration_schema, diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index e96af57f5..6e789ec92 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -676,7 +676,7 @@ def test_create_pulse_sequence(aws_session, arn, pulse_sequence): "}", ] ) - expected_program = OpenQASMProgram(source=expected_openqasm) + expected_program = OpenQASMProgram(source=expected_openqasm, inputs={}) aws_session.create_quantum_task.return_value = arn AwsQuantumTask.create(aws_session, SIMULATOR_ARN, pulse_sequence, S3_TARGET, 10) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 928cff757..6d8bcb6ad 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -740,7 +740,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -769,7 +769,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "bit[2] b;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -800,7 +800,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "OPENQASM 3.0;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -835,7 +835,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[5] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -866,7 +866,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -899,7 +899,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[5] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -933,7 +933,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[7] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -965,7 +965,7 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): "qubit[2] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + + "(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -1033,8 +1033,7 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): "bit[1] b;", "qubit[1] q;", "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal z $0, $1 {", " set_frequency(predefined_frame_1, 6000000.0);", @@ -1131,8 +1130,7 @@ def foo( "bit[1] b;", "qubit[1] q;", "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3000000.0ns, 400000000.0ns, 0.2, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", "}", "defcal foo(-0.2) $0 {", " shift_phase(predefined_frame_1, -0.1);", @@ -3003,11 +3001,9 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): "bit[2] b;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(1000000.0ns, 700000000.0ns, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3000000.0ns, 400000000.0ns, 0.2, 1," - " false);", - " waveform drag_gauss_wf_2 = drag_gaussian(3000000.0ns, 400000000.0ns, " - "0.2, 1, false);", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1," " false);", + " waveform drag_gauss_wf_2 = drag_gaussian(3.0ms, 400.0ms, " "0.2, 1, false);", "}", "h $0;", "cal {", @@ -3120,7 +3116,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): "bit[2] b;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(10000.0ns, 700000000.0ns, 1, false);", + " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", "}", "rx(0.5) $0;", "cal {", @@ -3145,7 +3141,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): "bit[2] b;", "cal {", " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(10000.0ns, 700000000.0ns, 1, false);", + " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", "}", "rx(0.5) $0;", "cal {", diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py index c95ce74a3..de3fa2fc6 100644 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -91,7 +91,7 @@ def test_to_ir(pulse_sequence): "OPENQASM 3.0;", "defcal rx(1.0) $0, $1 {", " barrier test_frame_rf;", - " delay[1000000000000.0ns] test_frame_rf;", + " delay[1000s] test_frame_rf;", "}", ] ) @@ -111,7 +111,7 @@ def test_to_ir_with_bad_key(pulse_sequence): "OPENQASM 3.0;", "defcal z $0, $1 {", " barrier test_frame_rf;", - " delay[1000000000000.0ns] test_frame_rf;", + " delay[1000s] test_frame_rf;", "}", ] ) @@ -129,7 +129,7 @@ def test_to_ir_with_key(pulse_sequence): "OPENQASM 3.0;", "defcal z $0, $1 {", " barrier test_frame_rf;", - " delay[1000000000000.0ns] test_frame_rf;", + " delay[1000s] test_frame_rf;", "}", ] ) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index fc8fe7787..1b6ac56c7 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1048,8 +1048,8 @@ def to_ir(pulse_gate): assert a_bound_ir == "\n".join( [ "cal {", - " set_frequency(user_frame, b + 3);", - " delay[(1000000000.0*c)ns] user_frame;", + " set_frequency(user_frame, 3.0 + b);", + " delay[c * 1s] user_frame;", "}", ] ) diff --git a/test/unit_tests/braket/parametric/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py index 879706fe0..370d45083 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -157,6 +157,7 @@ def test_sub_return_expression(): (FreeParameter("a") + 2 * FreeParameter("b"), {"a": 0.1, "b": 0.3}, 0.7, float), (FreeParameter("x"), {"y": 1}, FreeParameter("x"), FreeParameter), (FreeParameter("y"), {"y": -0.1}, -0.1, float), + (2 * FreeParameter("i"), {"i": 1}, 2.0, float), ( FreeParameter("a") + 2 * FreeParameter("x"), {"a": 0.4, "b": 0.4}, diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 57ba20fbd..006326047 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -87,7 +87,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined .set_frequency(predefined_frame_1, param) .shift_frequency(predefined_frame_1, param) .set_phase(predefined_frame_1, param) - .shift_phase(predefined_frame_1, param) + .shift_phase(predefined_frame_1, -param) .set_scale(predefined_frame_1, param) .capture_v0(predefined_frame_1) .delay([predefined_frame_1, predefined_frame_2], param) @@ -125,25 +125,28 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " waveform gauss_wf = gaussian((1000000000.0*length_g)ns, (1000000000.0*sigma_g)ns, " - "1, false);", - " waveform drag_gauss_wf = " - "drag_gaussian((1000000000.0*length_dg)ns, (1000000000.0*sigma_dg)ns, 0.2, 1, false);", - " waveform constant_wf = constant((1000000000.0*length_c)ns, 2.0 + 0.3im);", + " input float length_c;", + " input float length_dg;", + " input float sigma_dg;", + " input float length_g;", + " input float sigma_g;", + " input float a;", + " input float b;", + " waveform gauss_wf = gaussian(length_g * 1s, sigma_g * 1s, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(length_dg * 1s," + " sigma_dg * 1s, 0.2, 1, false);", + " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", " bit[2] psb;", - " set_frequency(predefined_frame_1, a + 2*b);", - " shift_frequency(predefined_frame_1, a + 2*b);", - " set_phase(predefined_frame_1, a + 2*b);", - " shift_phase(predefined_frame_1, a + 2*b);", - " set_scale(predefined_frame_1, a + 2*b);", + " set_frequency(predefined_frame_1, a + 2.0 * b);", + " shift_frequency(predefined_frame_1, a + 2.0 * b);", + " set_phase(predefined_frame_1, a + 2.0 * b);", + " shift_phase(predefined_frame_1, -1.0 * a + -2.0 * b);", + " set_scale(predefined_frame_1, a + 2.0 * b);", " psb[0] = capture_v0(predefined_frame_1);", - ( - " delay[(1000000000.0*a + 2000000000.0*b)ns]" - " predefined_frame_1, predefined_frame_2;" - ), - " delay[(1000000000.0*a + 2000000000.0*b)ns] predefined_frame_1;", - " delay[1000000.0ns] predefined_frame_1;", + " delay[(a + 2.0 * b) * 1s] predefined_frame_1, predefined_frame_2;", + " delay[(a + 2.0 * b) * 1s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", " play(predefined_frame_2, drag_gauss_wf);", @@ -173,21 +176,22 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " waveform gauss_wf = gaussian(1000000.0ns, (1000000000.0*sigma_g)ns, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3000000.0ns, 400000000.0ns, 0.2, 1," - " false);", - " waveform constant_wf = constant(4000000.0ns, 2.0 + 0.3im);", + " input float sigma_g;", + " input float a;", + " waveform gauss_wf = gaussian(1.0ms, sigma_g * 1s, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", " bit[2] psb;", - " set_frequency(predefined_frame_1, a + 4);", - " shift_frequency(predefined_frame_1, a + 4);", - " set_phase(predefined_frame_1, a + 4);", - " shift_phase(predefined_frame_1, a + 4);", - " set_scale(predefined_frame_1, a + 4);", + " set_frequency(predefined_frame_1, a + 4.0);", + " shift_frequency(predefined_frame_1, a + 4.0);", + " set_phase(predefined_frame_1, a + 4.0);", + " shift_phase(predefined_frame_1, -1.0 * a + -4.0);", + " set_scale(predefined_frame_1, a + 4.0);", " psb[0] = capture_v0(predefined_frame_1);", - " delay[(1000000000.0*a + 4000000000.0)ns] predefined_frame_1, predefined_frame_2;", - " delay[(1000000000.0*a + 4000000000.0)ns] predefined_frame_1;", - " delay[1000000.0ns] predefined_frame_1;", + " delay[(a + 4.0) * 1s] predefined_frame_1, predefined_frame_2;", + " delay[(a + 4.0) * 1s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", " play(predefined_frame_2, drag_gauss_wf);", @@ -199,28 +203,27 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined ) assert b_bound.to_ir() == b_bound_call.to_ir() == expected_str_b_bound assert pulse_sequence.to_ir() == expected_str_unbound - assert b_bound.parameters == set([FreeParameter("sigma_g"), FreeParameter("a")]) + assert b_bound.parameters == {FreeParameter("sigma_g"), FreeParameter("a")} both_bound = b_bound.make_bound_pulse_sequence({"a": 1, "sigma_g": 0.7}) both_bound_call = b_bound_call(1, sigma_g=0.7) # use arg 1 for a expected_str_both_bound = "\n".join( [ "OPENQASM 3.0;", "cal {", - " waveform gauss_wf = gaussian(1000000.0ns, 700000000.0ns, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3000000.0ns, 400000000.0ns, 0.2, 1," - " false);", - " waveform constant_wf = constant(4000000.0ns, 2.0 + 0.3im);", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", " bit[2] psb;", - " set_frequency(predefined_frame_1, 5);", - " shift_frequency(predefined_frame_1, 5);", - " set_phase(predefined_frame_1, 5);", - " shift_phase(predefined_frame_1, 5);", - " set_scale(predefined_frame_1, 5);", + " set_frequency(predefined_frame_1, 5.0);", + " shift_frequency(predefined_frame_1, 5.0);", + " set_phase(predefined_frame_1, 5.0);", + " shift_phase(predefined_frame_1, -5.0);", + " set_scale(predefined_frame_1, 5.0);", " psb[0] = capture_v0(predefined_frame_1);", - " delay[5000000000.00000ns] predefined_frame_1, predefined_frame_2;", - " delay[5000000000.00000ns] predefined_frame_1;", - " delay[1000000.0ns] predefined_frame_1;", + " delay[5.0s] predefined_frame_1, predefined_frame_2;", + " delay[5.0s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", " play(predefined_frame_2, drag_gauss_wf);", @@ -311,10 +314,9 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): [ "OPENQASM 3.0;", "cal {", - " waveform gauss_wf = gaussian(1000000.0ns, 700000000.0ns, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3000000.0ns, 400000000.0ns, 0.2, 1," - " false);", - " waveform constant_wf = constant(4000000.0ns, 2.0 + 0.3im);", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", " bit[2] psb;", " set_frequency(predefined_frame_1, 3000000000.0);", @@ -324,8 +326,8 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): " set_scale(predefined_frame_1, 0.25);", " psb[0] = capture_v0(predefined_frame_1);", " delay[2.0ns] predefined_frame_1, predefined_frame_2;", - " delay[1000.0ns] predefined_frame_1;", - " delay[1000000.0ns] $0;", + " delay[1.0us] predefined_frame_1;", + " delay[1.0ms] $0;", " barrier $0, $1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index b42eacc0b..0dd8f4ea5 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -42,6 +42,14 @@ def test_arbitrary_waveform(amps): assert oq_exp.name == wf.id +def test_arbitrary_waveform_repr(): + amps = [1, 4, 5] + id = "arb_wf_x" + wf = ArbitraryWaveform(amps, id) + expected = f"ArbitraryWaveform('id': {wf.id}, 'amplitudes': {wf.amplitudes})" + assert repr(wf) == expected + + def test_arbitrary_waveform_default_params(): amps = [1, 4, 5] wf = ArbitraryWaveform(amps) @@ -74,7 +82,16 @@ def test_constant_waveform(): assert wf.iq == iq assert wf.id == id - _assert_wf_qasm(wf, "waveform const_wf_x = constant(4000000.0ns, 4);") + _assert_wf_qasm(wf, "waveform const_wf_x = constant(4.0ms, 4);") + + +def test_constant_waveform_repr(): + length = 4e-3 + iq = 4 + id = "const_wf_x" + wf = ConstantWaveform(length, iq, id) + expected = f"ConstantWaveform('id': {wf.id}, 'length': {wf.length}, 'iq': {wf.iq})" + assert repr(wf) == expected def test_constant_waveform_default_params(): @@ -101,14 +118,15 @@ def test_constant_wf_free_params(): assert wf.parameters == [FreeParameter("length_v") + FreeParameter("length_w")] _assert_wf_qasm( wf, - "waveform const_wf = " - "constant((1000000000.0*length_v + 1000000000.0*length_w)ns, 2.0 - 3.0im);", + "input float length_v;\n" + "input float length_w;\n" + "waveform const_wf = constant((length_v + length_w) * 1s, 2.0 - 3.0im);", ) wf_2 = wf.bind_values(length_v=2e-6, length_w=4e-6) assert len(wf_2.parameters) == 1 assert math.isclose(wf_2.parameters[0], 6e-6) - _assert_wf_qasm(wf_2, "waveform const_wf = constant(6000.0ns, 2.0 - 3.0im);") + _assert_wf_qasm(wf_2, "waveform const_wf = constant(6.0us, 2.0 - 3.0im);") def test_drag_gaussian_waveform(): @@ -126,9 +144,22 @@ def test_drag_gaussian_waveform(): assert wf.sigma == sigma assert wf.length == length - _assert_wf_qasm( - wf, "waveform drag_gauss_wf = drag_gaussian(4.0ns, 300000000.0ns, 0.6, 0.4, false);" + _assert_wf_qasm(wf, "waveform drag_gauss_wf = drag_gaussian(4.0ns, 300.0ms, 0.6, 0.4, false);") + + +def test_drag_gaussian_waveform_repr(): + length = 4e-9 + sigma = 0.3 + beta = 0.6 + amplitude = 0.4 + zero_at_edges = False + id = "drag_gauss_wf" + wf = DragGaussianWaveform(length, sigma, beta, amplitude, zero_at_edges, id) + expected = ( + f"DragGaussianWaveform('id': {wf.id}, 'length': {wf.length}, 'sigma': {wf.sigma}, " + f"'beta': {wf.beta}, 'amplitude': {wf.amplitude}, 'zero_at_edges': {wf.zero_at_edges})" ) + assert repr(wf) == expected def test_drag_gaussian_waveform_default_params(): @@ -154,22 +185,6 @@ def test_drag_gaussian_wf_eq(): assert wf != wfc -def test_gaussian_waveform(): - length = 4e-9 - sigma = 0.3 - amplitude = 0.4 - zero_at_edges = False - id = "gauss_wf" - wf = GaussianWaveform(length, sigma, amplitude, zero_at_edges, id) - assert wf.id == id - assert wf.zero_at_edges == zero_at_edges - assert wf.amplitude == amplitude - assert wf.sigma == sigma - assert wf.length == length - - _assert_wf_qasm(wf, "waveform gauss_wf = gaussian(4.0ns, 300000000.0ns, 0.4, false);") - - def test_drag_gaussian_wf_free_params(): wf = DragGaussianWaveform( FreeParameter("length_v"), @@ -186,9 +201,14 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf, + "input float length_v;\n" + "input float sigma_a;\n" + "input float sigma_b;\n" + "input float beta_y;\n" + "input float amp_z;\n" "waveform d_gauss_wf = " - "drag_gaussian((1000000000.0*length_v)ns, (1000000000.0*sigma_a + " - "1000000000.0*sigma_b)ns, beta_y, amp_z, false);", + "drag_gaussian(length_v * 1s, (sigma_a + " + "sigma_b) * 1s, beta_y, amp_z, false);", ) wf_2 = wf.bind_values(length_v=0.6, sigma_a=0.4) @@ -200,15 +220,45 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf_2, - "waveform d_gauss_wf = drag_gaussian(600000000.0ns, (1000000000.0*sigma_b " - "+ 400000000.0)ns, beta_y, amp_z, false);", + "input float sigma_b;\n" + "input float beta_y;\n" + "input float amp_z;\n" + "waveform d_gauss_wf = drag_gaussian(600.0ms, (0.4 + sigma_b) * 1s, beta_y, amp_z, false);", ) wf_3 = wf.bind_values(length_v=0.6, sigma_a=0.3, sigma_b=0.1, beta_y=0.2, amp_z=0.1) assert wf_3.parameters == [0.6, 0.4, 0.2, 0.1] - _assert_wf_qasm( - wf_3, "waveform d_gauss_wf = drag_gaussian(600000000.0ns, 400000000.0ns, 0.2, 0.1, false);" + _assert_wf_qasm(wf_3, "waveform d_gauss_wf = drag_gaussian(600.0ms, 400.0ms, 0.2, 0.1, false);") + + +def test_gaussian_waveform(): + length = 4e-9 + sigma = 0.3 + amplitude = 0.4 + zero_at_edges = False + id = "gauss_wf" + wf = GaussianWaveform(length, sigma, amplitude, zero_at_edges, id) + assert wf.id == id + assert wf.zero_at_edges == zero_at_edges + assert wf.amplitude == amplitude + assert wf.sigma == sigma + assert wf.length == length + + _assert_wf_qasm(wf, "waveform gauss_wf = gaussian(4.0ns, 300.0ms, 0.4, false);") + + +def test_gaussian_waveform_repr(): + length = 4e-9 + sigma = 0.3 + amplitude = 0.4 + zero_at_edges = False + id = "gauss_wf" + wf = GaussianWaveform(length, sigma, amplitude, zero_at_edges, id) + expected = ( + f"GaussianWaveform('id': {wf.id}, 'length': {wf.length}, 'sigma': {wf.sigma}, " + f"'amplitude': {wf.amplitude}, 'zero_at_edges': {wf.zero_at_edges})" ) + assert repr(wf) == expected def test_gaussian_waveform_default_params(): @@ -243,19 +293,21 @@ def test_gaussian_wf_free_params(): ] _assert_wf_qasm( wf, - "waveform gauss_wf = gaussian((1000000000.0*length_v)ns, (1000000000.0*sigma_x)ns, " - "amp_z, false);", + "input float length_v;\n" + "input float sigma_x;\n" + "input float amp_z;\n" + "waveform gauss_wf = gaussian(length_v * 1s, sigma_x * 1s, amp_z, false);", ) wf_2 = wf.bind_values(length_v=0.6, sigma_x=0.4) assert wf_2.parameters == [0.6, 0.4, FreeParameter("amp_z")] _assert_wf_qasm( - wf_2, "waveform gauss_wf = gaussian(600000000.0ns, 400000000.0ns, amp_z, false);" + wf_2, "input float amp_z;\nwaveform gauss_wf = gaussian(600.0ms, 400.0ms, amp_z, false);" ) wf_3 = wf.bind_values(length_v=0.6, sigma_x=0.3, amp_z=0.1) assert wf_3.parameters == [0.6, 0.3, 0.1] - _assert_wf_qasm(wf_3, "waveform gauss_wf = gaussian(600000000.0ns, 300000000.0ns, 0.1, false);") + _assert_wf_qasm(wf_3, "waveform gauss_wf = gaussian(600.0ms, 300.0ms, 0.1, false);") def _assert_wf_qasm(waveform, expected_qasm): From 29775203e20470c5890e8358a8cb603997f79ae4 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 6 Feb 2024 16:17:25 +0000 Subject: [PATCH 044/347] prepare release v1.69.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5207697a4..54fdf51a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.69.0 (2024-02-06) + +### Features + + * update OQpy to version 0.3.5 + ## v1.68.3 (2024-02-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 764a6422e..d12b14c82 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.68.4.dev0" +__version__ = "1.69.0" From 2f1de8a41fc7666f34f4e3411fb6a3f2a6a20b6e Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 6 Feb 2024 16:17:25 +0000 Subject: [PATCH 045/347] update development version to v1.69.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d12b14c82..94880a4eb 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.69.0" +__version__ = "1.69.1.dev0" From 9d8b741d904b740c17efaa1a50f46d92c87ef02a Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:00:17 -0800 Subject: [PATCH 046/347] =?UTF-8?q?fix:=20let=20price=20tracker=20checks?= =?UTF-8?q?=20skip=20over=20devices=20without=20execution=20win=E2=80=A6?= =?UTF-8?q?=20(#866)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: let price tracker checks skip over devices without execution windows * tox formatting fix * Update test_cost_tracking.py Co-authored-by: Lauren Capelluto * tox formatting fix --------- Co-authored-by: Abe Coull Co-authored-by: Lauren Capelluto --- test/integ_tests/test_cost_tracking.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 7e97c6f78..d638f80b0 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -20,12 +20,9 @@ from braket.aws import AwsDevice, AwsSession from braket.circuits import Circuit -from braket.devices import Devices from braket.tracking import Tracker from braket.tracking.tracker import MIN_SIMULATOR_DURATION -_RESERVATION_ONLY_DEVICES = {Devices.IonQ.Forte1} - @pytest.mark.parametrize( "qpu", @@ -94,7 +91,8 @@ def test_all_devices_price_search(): tasks = {} for region in AwsDevice.REGIONS: s = AwsSession(boto3.Session(region_name=region)) - for device in [device for device in devices if device.arn not in _RESERVATION_ONLY_DEVICES]: + # Skip devices with empty execution windows + for device in [device for device in devices if device.properties.service.executionWindows]: try: s.get_device(device.arn) From 6818b6e80bf47d0854a4c9580a7c48aa85994b8b Mon Sep 17 00:00:00 2001 From: Angela Guo Date: Wed, 7 Feb 2024 15:11:52 -0800 Subject: [PATCH 047/347] infra: update schema version to latest in setup.py (#870) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1e85974d5..b336a2790 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.19.1", + "amazon-braket-schemas>=1.20.2", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", From 20686bc3b4715fc61bec471e8d2d42cde65c9057 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Feb 2024 16:16:00 +0000 Subject: [PATCH 048/347] prepare release v1.69.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54fdf51a2..08342f901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.69.1 (2024-02-08) + +### Bug Fixes and Other Changes + + * let price tracker checks skip over devices without execution win… + ## v1.69.0 (2024-02-06) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 94880a4eb..ab9a50ffc 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.69.1.dev0" +__version__ = "1.69.1" From adde1afca52936d7e0b2c37f19bd6eae11247db5 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 8 Feb 2024 16:16:00 +0000 Subject: [PATCH 049/347] update development version to v1.69.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ab9a50ffc..f459c803b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.69.1" +__version__ = "1.69.2.dev0" From 671ea84b9f7d593ace21c30578f28733be4630c7 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 8 Feb 2024 18:51:59 -0500 Subject: [PATCH 050/347] feat: Support noise models in DM simulators (#850) Support initializing density matrix simulators with a noise model, including the local DM simulator and DM1. Noises are automatically applied to circuits upon calling `device.run`. --- src/braket/aws/aws_device.py | 21 +- src/braket/circuits/translations.py | 13 + src/braket/devices/device.py | 45 +++- src/braket/devices/local_simulator.py | 23 +- .../braket/aws/common_test_utils.py | 1 + test/unit_tests/braket/aws/test_aws_device.py | 156 +++++++++++- .../braket/devices/test_local_simulator.py | 230 +++++++++++++++++- 7 files changed, 476 insertions(+), 13 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 14adacb3a..ee9c4128b 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -33,6 +33,7 @@ from braket.aws.queue_information import QueueDepthInfo, QueueType from braket.circuits import Circuit, Gate, QubitSet from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.noise_model import NoiseModel from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties from braket.device_schema.dwave import DwaveProviderProperties from braket.device_schema.pulse.pulse_device_action_properties_v1 import ( # noqa TODO: Remove device_action module once this is added to init in the schemas repo @@ -78,11 +79,19 @@ class AwsDevice(Device): "Xy": "XY", } - def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): + def __init__( + self, + arn: str, + aws_session: Optional[AwsSession] = None, + noise_model: Optional[NoiseModel] = None, + ): """ Args: arn (str): The ARN of the device aws_session (Optional[AwsSession]): An AWS session object. Default is `None`. + noise_model (Optional[NoiseModel]): The Braket noise model to apply to the circuit + before execution. Noise model can only be added to the devices that support + noise simulation. Note: Some devices (QPUs) are physically located in specific AWS Regions. In some cases, @@ -104,6 +113,9 @@ def __init__(self, arn: str, aws_session: Optional[AwsSession] = None): self._aws_session = self._get_session_and_initialize(aws_session or AwsSession()) self._ports = None self._frames = None + if noise_model: + self._validate_device_noise_model_support(noise_model) + self._noise_model = noise_model def run( self, @@ -189,6 +201,8 @@ def run( See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` """ + if self._noise_model: + task_specification = self._apply_noise_model_to_circuit(task_specification) return AwsQuantumTask.create( self._aws_session, self._arn, @@ -284,6 +298,11 @@ def run_batch( See Also: `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` """ + if self._noise_model: + task_specifications = [ + self._apply_noise_model_to_circuit(task_specification) + for task_specification in task_specifications + ] return AwsQuantumTaskBatch( AwsSession.copy_session(self._aws_session, max_connections=max_connections), self._arn, diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index bbb194be3..74dae1bbd 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -84,6 +84,19 @@ "phase_damping": noises.PhaseDamping, } +SUPPORTED_NOISE_PRAGMA_TO_NOISE = { + "braket_noise_bit_flip": noises.BitFlip, + "braket_noise_phase_flip": noises.PhaseFlip, + "braket_noise_pauli_channel": noises.PauliChannel, + "braket_noise_depolarizing": noises.Depolarizing, + "braket_noise_two_qubit_depolarizing": noises.TwoQubitDepolarizing, + "braket_noise_two_qubit_dephasing": noises.TwoQubitDephasing, + "braket_noise_amplitude_damping": noises.AmplitudeDamping, + "braket_noise_generalized_amplitude_damping": noises.GeneralizedAmplitudeDamping, + "braket_noise_phase_damping": noises.PhaseDamping, + "braket_noise_kraus": noises.Kraus, +} + def get_observable(obs: Union[models.Observable, list]) -> Observable: return _get_observable(obs) diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 49f510edf..d61f90162 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -11,11 +11,17 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import warnings from abc import ABC, abstractmethod from typing import Optional, Union +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem -from braket.circuits import Circuit +from braket.circuits import Circuit, Noise +from braket.circuits.noise_model import NoiseModel +from braket.circuits.translations import SUPPORTED_NOISE_PRAGMA_TO_NOISE +from braket.device_schema import DeviceActionType +from braket.ir.openqasm import Program from braket.tasks.quantum_task import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch @@ -39,7 +45,7 @@ def run( shots: Optional[int], inputs: Optional[dict[str, float]], *args, - **kwargs + **kwargs, ) -> QuantumTask: """Run a quantum task specification on this quantum device. A quantum task can be a circuit or an annealing problem. @@ -68,7 +74,7 @@ def run_batch( max_parallel: Optional[int], inputs: Optional[Union[dict[str, float], list[dict[str, float]]]], *args, - **kwargs + **kwargs, ) -> QuantumTaskBatch: """Executes a batch of quantum tasks in parallel @@ -104,3 +110,36 @@ def status(self) -> str: str: The status of this Device """ return self._status + + def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: + supported_noises = set( + SUPPORTED_NOISE_PRAGMA_TO_NOISE[pragma].__name__ + for pragma in self.properties.action[DeviceActionType.OPENQASM].supportedPragmas + if pragma in SUPPORTED_NOISE_PRAGMA_TO_NOISE + ) + noise_operators = set(noise_instr.noise.name for noise_instr in noise_model._instructions) + if not noise_operators <= supported_noises: + raise ValueError( + f"{self.name} does not support noise simulation or the noise model includes noise " + + f"that is not supported by {self.name}." + ) + + def _apply_noise_model_to_circuit( + self, task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation] + ) -> None: + if isinstance(task_specification, Circuit): + for instruction in task_specification.instructions: + if isinstance(instruction.operator, Noise): + warnings.warn( + "The noise model of the device is applied to a circuit that already has" + " noise instructions." + ) + break + task_specification = self._noise_model.apply(task_specification) + else: + warnings.warn( + "Noise model is only applicable to circuits. The type of the task specification is" + f" {task_specification.__class__.__name__}. The noise model of the device does not" + " apply." + ) + return task_specification diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index c719978ac..3140e8534 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -25,6 +25,7 @@ from braket.annealing.problem import Problem from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.circuits.noise_model import NoiseModel from braket.circuits.serialization import IRType from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.devices.device import Device @@ -50,12 +51,19 @@ class LocalSimulator(Device): results using constructs from the SDK rather than Braket IR. """ - def __init__(self, backend: Union[str, BraketSimulator] = "default"): + def __init__( + self, + backend: Union[str, BraketSimulator] = "default", + noise_model: Optional[NoiseModel] = None, + ): """ Args: backend (Union[str, BraketSimulator]): The name of the simulator backend or the actual simulator instance to use for simulation. Defaults to the `default` simulator backend name. + noise_model (Optional[NoiseModel]): The Braket noise model to apply to the circuit + before execution. Noise model can only be added to the devices that support + noise simulation. """ delegate = self._get_simulator(backend) super().__init__( @@ -63,6 +71,9 @@ def __init__(self, backend: Union[str, BraketSimulator] = "default"): status="AVAILABLE", ) self._delegate = delegate + if noise_model: + self._validate_device_noise_model_support(noise_model) + self._noise_model = noise_model def run( self, @@ -98,10 +109,12 @@ def run( >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) """ + if self._noise_model: + task_specification = self._apply_noise_model_to_circuit(task_specification) result = self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) return LocalQuantumTask(result) - def run_batch( + def run_batch( # noqa: C901 self, task_specifications: Union[ Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], @@ -134,6 +147,12 @@ def run_batch( """ inputs = inputs or {} + if self._noise_model: + task_specifications = [ + self._apply_noise_model_to_circuit(task_specification) + for task_specification in task_specifications + ] + if not max_parallel: max_parallel = cpu_count() diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index f1d6d96df..d9d6c3d44 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -21,6 +21,7 @@ IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" XANADU_ARN = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 29adf2517..cac1c1536 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -13,6 +13,7 @@ import io import json import os +import textwrap from datetime import datetime from unittest.mock import Mock, PropertyMock, patch from urllib.error import URLError @@ -21,6 +22,7 @@ import pytest from botocore.exceptions import ClientError from common_test_utils import ( + DM1_ARN, DWAVE_ARN, IONQ_ARN, OQC_ARN, @@ -35,8 +37,9 @@ from braket.aws import AwsDevice, AwsDeviceType, AwsQuantumTask from braket.aws.queue_information import QueueDepthInfo, QueueType -from braket.circuits import Circuit, FreeParameter, Gate, QubitSet +from braket.circuits import Circuit, FreeParameter, Gate, Noise, QubitSet from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.noise_model import GateCriteria, NoiseModel from braket.device_schema.device_execution_window import DeviceExecutionWindow from braket.device_schema.dwave import DwaveDeviceCapabilities from braket.device_schema.rigetti import RigettiDeviceCapabilities @@ -359,7 +362,59 @@ def test_d_wave_schema(): "actionType": "braket.ir.jaqcd.program", "version": ["1"], "supportedOperations": ["H"], - } + }, + }, + "paradigm": {"qubitCount": 30}, + "deviceParameters": {}, +} + +MOCK_GATE_MODEL_NOISE_SIMULATOR_CAPABILITIES_JSON = { + "braketSchemaHeader": { + "name": "braket.device_schema.simulators.gate_model_simulator_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], + "supportedResultTypes": [ + { + "name": "StateVector", + "observables": ["x", "y", "z"], + "minShots": 0, + "maxShots": 0, + }, + ], + "supportedPragmas": [ + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + "braket_unitary_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_density_matrix", + ], + }, }, "paradigm": {"qubitCount": 30}, "deviceParameters": {}, @@ -376,6 +431,18 @@ def test_gate_model_sim_schema(): ) +MOCK_GATE_MODEL_NOISE_SIMULATOR_CAPABILITIES = GateModelSimulatorDeviceCapabilities.parse_obj( + MOCK_GATE_MODEL_NOISE_SIMULATOR_CAPABILITIES_JSON +) + + +def test_gate_model_sim_schema(): + validate( + MOCK_GATE_MODEL_NOISE_SIMULATOR_CAPABILITIES_JSON, + GateModelSimulatorDeviceCapabilities.schema(), + ) + + MOCK_GATE_MODEL_SIMULATOR = { "deviceName": "SV1", "deviceType": "SIMULATOR", @@ -384,6 +451,16 @@ def test_gate_model_sim_schema(): "deviceCapabilities": MOCK_GATE_MODEL_SIMULATOR_CAPABILITIES.json(), } + +MOCK_GATE_MODEL_NOISE_SIMULATOR = { + "deviceName": "DM1", + "deviceType": "SIMULATOR", + "providerName": "provider1", + "deviceStatus": "ONLINE", + "deviceCapabilities": MOCK_GATE_MODEL_NOISE_SIMULATOR_CAPABILITIES.json(), +} + + MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( "amazon-braket-us-test-1-00000000", "tasks", @@ -2070,3 +2147,78 @@ def test_queue_depth(arn): quantum_tasks={QueueType.NORMAL: "19", QueueType.PRIORITY: "3"}, jobs="0 (3 prioritized job(s) running)", ) + + +@pytest.fixture +def noise_model(): + return ( + NoiseModel() + .add_noise(Noise.BitFlip(0.05), GateCriteria(Gate.H)) + .add_noise(Noise.TwoQubitDepolarizing(0.10), GateCriteria(Gate.CNot)) + ) + + +@patch.dict( + os.environ, + {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, +) +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_with_noise_model(aws_quantum_task_mock, aws_session_init, aws_session, noise_model): + arn = DM1_ARN + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_NOISE_SIMULATOR + device = AwsDevice(arn, noise_model=noise_model) + circuit = Circuit().h(0).cnot(0, 1) + _ = device.run(circuit) + + expected_circuit = textwrap.dedent( + """ + OPENQASM 3.0; + bit[2] b; + qubit[2] q; + h q[0]; + #pragma braket noise bit_flip(0.05) q[0] + cnot q[0], q[1]; + #pragma braket noise two_qubit_depolarizing(0.1) q[0], q[1] + b[0] = measure q[0]; + b[1] = measure q[1]; + """ + ).strip() + + expected_circuit = Circuit().h(0).bit_flip(0, 0.05).cnot(0, 1).two_qubit_depolarizing(0, 1, 0.1) + assert aws_quantum_task_mock.call_args_list[0][0][2] == expected_circuit + + +@patch.dict( + os.environ, + {"AMZN_BRAKET_TASK_RESULTS_S3_URI": "s3://env_bucket/env/path"}, +) +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_batch_with_noise_model( + aws_quantum_task_mock, aws_session_init, aws_session, noise_model +): + arn = DM1_ARN + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_NOISE_SIMULATOR + device = AwsDevice(arn, noise_model=noise_model) + circuit = Circuit().h(0).cnot(0, 1) + _ = device.run_batch([circuit] * 2) + + expected_circuit = textwrap.dedent( + """ + OPENQASM 3.0; + bit[2] b; + qubit[2] q; + h q[0]; + #pragma braket noise bit_flip(0.05) q[0] + cnot q[0], q[1]; + #pragma braket noise two_qubit_depolarizing(0.1) q[0], q[1] + b[0] = measure q[0]; + b[1] = measure q[1]; + """ + ).strip() + + expected_circuit = Circuit().h(0).bit_flip(0, 0.05).cnot(0, 1).two_qubit_depolarizing(0, 1, 0.1) + assert aws_quantum_task_mock.call_args_list[0][0][2] == expected_circuit diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 8485dc5e5..a2203ee60 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -11,8 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import json +import textwrap +import warnings from typing import Any, Dict, Optional -from unittest.mock import Mock +from unittest.mock import Mock, patch import pytest from pydantic import create_model # This is temporary for defining properties below @@ -22,8 +25,10 @@ from braket.ahs.atom_arrangement import AtomArrangement from braket.ahs.hamiltonian import Hamiltonian from braket.annealing import Problem, ProblemType -from braket.circuits import Circuit, FreeParameter -from braket.device_schema import DeviceCapabilities +from braket.circuits import Circuit, FreeParameter, Gate, Noise +from braket.circuits.noise_model import GateCriteria, NoiseModel, NoiseModelInstruction +from braket.device_schema import DeviceActionType, DeviceCapabilities +from braket.device_schema.openqasm_device_action_properties import OpenQASMDeviceActionProperties from braket.devices import LocalSimulator, local_simulator from braket.ir.openqasm import Program from braket.simulator import BraketSimulator @@ -200,7 +205,7 @@ def run( @property def properties(self) -> DeviceCapabilities: - return DeviceCapabilities.parse_obj( + device_properties = DeviceCapabilities.parse_obj( { "service": { "executionWindows": [ @@ -221,6 +226,87 @@ def properties(self) -> DeviceCapabilities: "deviceParameters": {}, } ) + oq3_action = OpenQASMDeviceActionProperties.parse_raw( + json.dumps( + { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], + "supportedResultTypes": [ + {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_state_vector", + ], + } + ) + ) + device_properties.action[DeviceActionType.OPENQASM] = oq3_action + return device_properties + + +class DummyProgramDensityMatrixSimulator(BraketSimulator): + def run( + self, program: ir.openqasm.Program, shots: Optional[int], *args, **kwargs + ) -> Dict[str, Any]: + self._shots = shots + return GATE_MODEL_RESULT + + @property + def properties(self) -> DeviceCapabilities: + device_properties = DeviceCapabilities.parse_obj( + { + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": {}, + "deviceParameters": {}, + } + ) + oq3_action = OpenQASMDeviceActionProperties.parse_raw( + json.dumps( + { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], + "supportedResultTypes": [ + {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, + ], + "supportedPragmas": [ + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + "braket_unitary_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_density_matrix", + ], + } + ) + ) + device_properties.action[DeviceActionType.OPENQASM] = oq3_action + return device_properties class DummyAnnealingSimulator(BraketSimulator): @@ -284,13 +370,16 @@ def properties(self) -> DeviceCapabilities: mock_circuit_entry = Mock() mock_program_entry = Mock() mock_jaqcd_entry = Mock() +mock_circuit_dm_entry = Mock() mock_circuit_entry.load.return_value = DummyCircuitSimulator mock_program_entry.load.return_value = DummyProgramSimulator mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator +mock_circuit_dm_entry.load.return_value = DummyProgramDensityMatrixSimulator local_simulator._simulator_devices = { "dummy": mock_circuit_entry, "dummy_oq3": mock_program_entry, "dummy_jaqcd": mock_jaqcd_entry, + "dummy_oq3_dm": mock_circuit_dm_entry, } mock_ahs_program = AnalogHamiltonianSimulation( @@ -490,7 +579,12 @@ def test_run_ahs(): def test_registered_backends(): - assert LocalSimulator.registered_backends() == {"dummy", "dummy_oq3", "dummy_jaqcd"} + assert LocalSimulator.registered_backends() == { + "dummy", + "dummy_oq3", + "dummy_jaqcd", + "dummy_oq3_dm", + } @pytest.mark.xfail(raises=TypeError) @@ -526,3 +620,129 @@ def test_properties(): sim = LocalSimulator(dummy) expected_properties = dummy.properties assert sim.properties == expected_properties + + +@pytest.fixture +def noise_model(): + return ( + NoiseModel() + .add_noise(Noise.BitFlip(0.05), GateCriteria(Gate.H)) + .add_noise(Noise.TwoQubitDepolarizing(0.10), GateCriteria(Gate.CNot)) + ) + + +@pytest.mark.parametrize("backend", ["dummy_oq3_dm"]) +def test_valid_local_device_for_noise_model(backend, noise_model): + device = LocalSimulator(backend, noise_model=noise_model) + assert device._noise_model.instructions == [ + NoiseModelInstruction(Noise.BitFlip(0.05), GateCriteria(Gate.H)), + NoiseModelInstruction(Noise.TwoQubitDepolarizing(0.10), GateCriteria(Gate.CNot)), + ] + + +@pytest.mark.parametrize("backend", ["dummy_oq3"]) +def test_invalid_local_device_for_noise_model(backend, noise_model): + with pytest.raises(ValueError): + _ = LocalSimulator(backend, noise_model=noise_model) + + +@pytest.mark.parametrize("backend", ["dummy_oq3_dm"]) +def test_local_device_with_invalid_noise_model(backend, noise_model): + with pytest.raises(TypeError): + _ = LocalSimulator(backend, noise_model=Mock()) + + +@patch.object(DummyProgramDensityMatrixSimulator, "run") +def test_run_with_noise_model(mock_run, noise_model): + mock_run.return_value = GATE_MODEL_RESULT + device = LocalSimulator("dummy_oq3_dm", noise_model=noise_model) + circuit = Circuit().h(0).cnot(0, 1) + _ = device.run(circuit, shots=4) + + expected_circuit = textwrap.dedent( + """ + OPENQASM 3.0; + bit[2] b; + qubit[2] q; + h q[0]; + #pragma braket noise bit_flip(0.05) q[0] + cnot q[0], q[1]; + #pragma braket noise two_qubit_depolarizing(0.1) q[0], q[1] + b[0] = measure q[0]; + b[1] = measure q[1]; + """ + ).strip() + + mock_run.assert_called_with( + Program(source=expected_circuit, inputs={}), + 4, + ) + + +@patch.object(LocalSimulator, "_apply_noise_model_to_circuit") +def test_run_batch_with_noise_model(mock_apply, noise_model): + device = LocalSimulator("dummy_oq3_dm", noise_model=noise_model) + circuit = Circuit().h(0).cnot(0, 1) + + mock_apply.return_value = noise_model.apply(circuit) + _ = device.run_batch([circuit] * 2, shots=4).results() + assert mock_apply.call_count == 2 + + +@patch.object(DummyProgramDensityMatrixSimulator, "run") +def test_run_noisy_circuit_with_noise_model(mock_run, noise_model): + mock_run.return_value = GATE_MODEL_RESULT + device = LocalSimulator("dummy_oq3_dm", noise_model=noise_model) + circuit = Circuit().h(0).depolarizing(0, 0.1) + with warnings.catch_warnings(record=True) as w: + _ = device.run(circuit, shots=4) + + expected_warning = ( + "The noise model of the device is applied to a circuit that already has noise " + "instructions." + ) + expected_circuit = textwrap.dedent( + """ + OPENQASM 3.0; + bit[1] b; + qubit[1] q; + h q[0]; + #pragma braket noise bit_flip(0.05) q[0] + #pragma braket noise depolarizing(0.1) q[0] + b[0] = measure q[0]; + """ + ).strip() + + mock_run.assert_called_with( + Program(source=expected_circuit, inputs={}), + 4, + ) + assert w[-1].message.__str__() == expected_warning + + +@patch.object(DummyProgramDensityMatrixSimulator, "run") +def test_run_openqasm_with_noise_model(mock_run, noise_model): + mock_run.return_value = GATE_MODEL_RESULT + device = LocalSimulator("dummy_oq3_dm", noise_model=noise_model) + expected_circuit = textwrap.dedent( + """ + OPENQASM 3.0; + bit[1] b; + qubit[1] q; + h q[0]; + b[0] = measure q[0]; + """ + ).strip() + expected_warning = ( + "Noise model is only applicable to circuits. The type of the task specification " + "is Program. The noise model of the device does not apply." + ) + circuit = Program(source=expected_circuit) + with warnings.catch_warnings(record=True) as w: + _ = device.run(circuit, shots=4) + + mock_run.assert_called_with( + Program(source=expected_circuit, inputs=None), + 4, + ) + assert w[-1].message.__str__() == expected_warning From 418000098aaac38419ce72b98b4e8a459974f1f8 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 12 Feb 2024 16:15:59 +0000 Subject: [PATCH 051/347] prepare release v1.70.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08342f901..1e26cc559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.70.0 (2024-02-12) + +### Features + + * Support noise models in DM simulators + ## v1.69.1 (2024-02-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f459c803b..a211f501e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.69.2.dev0" +__version__ = "1.70.0" From 14b207211524fe980b647190c2ca0406a883cedc Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 12 Feb 2024 16:15:59 +0000 Subject: [PATCH 052/347] update development version to v1.70.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a211f501e..99e2e0b96 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.0" +__version__ = "1.70.1.dev0" From ee915673e91effaab43e440b3feedafeb0922934 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 12 Feb 2024 22:34:05 -0500 Subject: [PATCH 053/347] Do not autodeclare FreeParameter in OQpy (#872) * do not declare FreeParameter * add test * linters * fix typos * fix coverage * fix docstring * fix type hints * do not sort parameters during to_ir --- src/braket/circuits/circuit.py | 28 ++++++-- .../parametric/free_parameter_expression.py | 4 +- src/braket/pulse/pulse_sequence.py | 4 ++ .../braket/circuits/test_circuit.py | 68 +++++++++++++++++++ .../braket/pulse/test_pulse_sequence.py | 42 +++++------- .../unit_tests/braket/pulse/test_waveforms.py | 20 +----- 6 files changed, 119 insertions(+), 47 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index ec44dec84..adebae07d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1235,6 +1235,7 @@ def _create_openqasm_header( gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], ) -> list[str]: ir_instructions = ["OPENQASM 3.0;"] + frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) for parameter in self.parameters: ir_instructions.append(f"input float {parameter};") if not self.result_types: @@ -1249,7 +1250,6 @@ def _create_openqasm_header( f"{serialization_properties.qubit_reference_type} supplied." ) - frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) if frame_wf_declarations: ir_instructions.append(frame_wf_declarations) return ir_instructions @@ -1269,8 +1269,20 @@ def _validate_gate_calbrations_uniqueness( waveforms[waveform.id] = waveform def _generate_frame_wf_defcal_declarations( - self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] - ) -> Optional[str]: + self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None + ) -> str | None: + """Generates the header where frames, waveforms and defcals are declared. + + It also adds any FreeParameter of the calibrations to the circuit parameter set. + + Args: + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): The + calibration data for the device. + + Returns: + str | None: An OpenQASM string + """ + program = oqpy.Program(None, simplify_constants=False) frames, waveforms = self._get_frames_waveforms_from_instrs(gate_definitions) @@ -1299,7 +1311,15 @@ def _generate_frame_wf_defcal_declarations( continue gate_name = gate._qasm_name - arguments = gate.parameters if isinstance(gate, Parameterizable) else None + arguments = gate.parameters if isinstance(gate, Parameterizable) else [] + + for param in calibration.parameters: + self._parameters.add(param) + arguments = [ + param._to_oqpy_expression() if isinstance(param, FreeParameter) else param + for param in arguments + ] + with oqpy.defcal( program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments ): diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 98916fbf5..fdfa1469a 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -191,7 +191,9 @@ def _to_oqpy_expression(self) -> OQPyExpression: elif isinstance(self.expression, sympy.Number): return float(self.expression) else: - fvar = FloatVar(name=self.expression.name, init_expression="input") + fvar = FloatVar( + name=self.expression.name, init_expression="input", needs_declaration=False + ) fvar.size = None fvar.type.size = None return fvar diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 25e5c3b55..2c84507d3 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -311,6 +311,10 @@ def to_ir(self) -> str: str: a str representing the OpenPulse program encoding the PulseSequence. """ program = deepcopy(self._program) + program.autodeclare(encal=False) + for param in self.parameters: + program.declare(param._to_oqpy_expression(), to_beginning=True) + if self._capture_v0_count: register_identifier = "psb" program.declare( diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 6d8bcb6ad..3cf40fd89 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -147,6 +147,25 @@ def pulse_sequence_2(predefined_frame_1): ) +@pytest.fixture +def pulse_sequence_3(predefined_frame_1): + return ( + PulseSequence() + .shift_phase( + predefined_frame_1, + FreeParameter("alpha"), + ) + .shift_phase( + predefined_frame_1, + FreeParameter("beta"), + ) + .play( + predefined_frame_1, + DragGaussianWaveform(length=3e-3, sigma=0.4, beta=0.2, id="drag_gauss_wf"), + ) + ) + + @pytest.fixture def gate_calibrations(pulse_sequence, pulse_sequence_2): calibration_key = (Gate.Z(), QubitSet([0, 1])) @@ -1013,6 +1032,55 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, assert copy_of_gate_calibrations.pulse_sequences == gate_calibrations.pulse_sequences +@pytest.mark.parametrize( + "circuit, calibration_key, expected_ir", + [ + ( + Circuit().rx(0, 0.2), + (Gate.Rx(FreeParameter("alpha")), QubitSet(0)), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "input float beta;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian(3.0ms," + " 400.0ms, 0.2, 1, false);", + "}", + "defcal rx(0.2) $0 {", + " shift_phase(predefined_frame_1, 0.2);", + " shift_phase(predefined_frame_1, beta);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.2) q[0];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ), + ), + ], +) +def test_circuit_with_parametric_defcal(circuit, calibration_key, expected_ir, pulse_sequence_3): + serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) + gate_calibrations = GateCalibrations( + { + calibration_key: pulse_sequence_3, + } + ) + + assert ( + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + gate_definitions=gate_calibrations.pulse_sequences, + ) + == expected_ir + ) + + def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): circ = Circuit().h(0, power=-2.5).h(0, power=0).rx(0, angle=FreeParameter("theta")) serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 006326047..da8e0a8a3 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -125,19 +125,16 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " input float length_c;", - " input float length_dg;", - " input float sigma_dg;", - " input float length_g;", - " input float sigma_g;", - " input float a;", - " input float b;", + " bit[2] psb;", + *[ + f" input float {parameter};" + for parameter in reversed(list(pulse_sequence.parameters)) + ], " waveform gauss_wf = gaussian(length_g * 1s, sigma_g * 1s, 1, false);", " waveform drag_gauss_wf = drag_gaussian(length_dg * 1s," " sigma_dg * 1s, 0.2, 1, false);", " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " bit[2] psb;", " set_frequency(predefined_frame_1, a + 2.0 * b);", " shift_frequency(predefined_frame_1, a + 2.0 * b);", " set_phase(predefined_frame_1, a + 2.0 * b);", @@ -157,17 +154,15 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined ] ) assert pulse_sequence.to_ir() == expected_str_unbound - assert pulse_sequence.parameters == set( - [ - FreeParameter("a"), - FreeParameter("b"), - FreeParameter("length_g"), - FreeParameter("length_dg"), - FreeParameter("sigma_g"), - FreeParameter("sigma_dg"), - FreeParameter("length_c"), - ] - ) + assert pulse_sequence.parameters == { + FreeParameter("a"), + FreeParameter("b"), + FreeParameter("length_g"), + FreeParameter("length_dg"), + FreeParameter("sigma_g"), + FreeParameter("sigma_dg"), + FreeParameter("length_c"), + } b_bound = pulse_sequence.make_bound_pulse_sequence( {"b": 2, "length_g": 1e-3, "length_dg": 3e-3, "sigma_dg": 0.4, "length_c": 4e-3} ) @@ -176,13 +171,12 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", - " input float sigma_g;", - " input float a;", + " bit[2] psb;", + *[f" input float {parameter};" for parameter in reversed(list(b_bound.parameters))], " waveform gauss_wf = gaussian(1.0ms, sigma_g * 1s, 1, false);", " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " bit[2] psb;", " set_frequency(predefined_frame_1, a + 4.0);", " shift_frequency(predefined_frame_1, a + 4.0);", " set_phase(predefined_frame_1, a + 4.0);", @@ -210,11 +204,11 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined [ "OPENQASM 3.0;", "cal {", + " bit[2] psb;", " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " bit[2] psb;", " set_frequency(predefined_frame_1, 5.0);", " shift_frequency(predefined_frame_1, 5.0);", " set_phase(predefined_frame_1, 5.0);", @@ -314,11 +308,11 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): [ "OPENQASM 3.0;", "cal {", + " bit[2] psb;", " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " bit[2] psb;", " set_frequency(predefined_frame_1, 3000000000.0);", " shift_frequency(predefined_frame_1, 1000000000.0);", " set_phase(predefined_frame_1, -0.5);", diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 0dd8f4ea5..0c56d3542 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -118,8 +118,6 @@ def test_constant_wf_free_params(): assert wf.parameters == [FreeParameter("length_v") + FreeParameter("length_w")] _assert_wf_qasm( wf, - "input float length_v;\n" - "input float length_w;\n" "waveform const_wf = constant((length_v + length_w) * 1s, 2.0 - 3.0im);", ) @@ -201,14 +199,8 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf, - "input float length_v;\n" - "input float sigma_a;\n" - "input float sigma_b;\n" - "input float beta_y;\n" - "input float amp_z;\n" "waveform d_gauss_wf = " - "drag_gaussian(length_v * 1s, (sigma_a + " - "sigma_b) * 1s, beta_y, amp_z, false);", + "drag_gaussian(length_v * 1s, (sigma_a + sigma_b) * 1s, beta_y, amp_z, false);", ) wf_2 = wf.bind_values(length_v=0.6, sigma_a=0.4) @@ -220,9 +212,6 @@ def test_drag_gaussian_wf_free_params(): ] _assert_wf_qasm( wf_2, - "input float sigma_b;\n" - "input float beta_y;\n" - "input float amp_z;\n" "waveform d_gauss_wf = drag_gaussian(600.0ms, (0.4 + sigma_b) * 1s, beta_y, amp_z, false);", ) @@ -293,17 +282,12 @@ def test_gaussian_wf_free_params(): ] _assert_wf_qasm( wf, - "input float length_v;\n" - "input float sigma_x;\n" - "input float amp_z;\n" "waveform gauss_wf = gaussian(length_v * 1s, sigma_x * 1s, amp_z, false);", ) wf_2 = wf.bind_values(length_v=0.6, sigma_x=0.4) assert wf_2.parameters == [0.6, 0.4, FreeParameter("amp_z")] - _assert_wf_qasm( - wf_2, "input float amp_z;\nwaveform gauss_wf = gaussian(600.0ms, 400.0ms, amp_z, false);" - ) + _assert_wf_qasm(wf_2, "waveform gauss_wf = gaussian(600.0ms, 400.0ms, amp_z, false);") wf_3 = wf.bind_values(length_v=0.6, sigma_x=0.3, amp_z=0.1) assert wf_3.parameters == [0.6, 0.3, 0.1] From 3917b30b80df4c352729d5433465d028ed686d22 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 13 Feb 2024 16:15:46 +0000 Subject: [PATCH 054/347] prepare release v1.70.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e26cc559..3682e0212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.70.1 (2024-02-13) + +### Bug Fixes and Other Changes + + * Do not autodeclare FreeParameter in OQpy + ## v1.70.0 (2024-02-12) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 99e2e0b96..2d23afac9 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.1.dev0" +__version__ = "1.70.1" From d09d3a42ed9689cf37bca5fb8da16ae99566fe6e Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 13 Feb 2024 16:15:46 +0000 Subject: [PATCH 055/347] update development version to v1.70.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2d23afac9..9ed071fb1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.1" +__version__ = "1.70.2.dev0" From 8cc8b3bf5fd6b0630a1e174a8dd8c2b60689632a Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:12:54 -0500 Subject: [PATCH 056/347] fix: Sort input parameters when doing testing equality of two PulseSequences (#874) * sort input parameters when doing testing equality of two PulseSequences * fix docstrings --- src/braket/pulse/pulse_sequence.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 2c84507d3..ac6f49067 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -304,15 +304,24 @@ def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequ return new_pulse_sequence - def to_ir(self) -> str: + def to_ir(self, sort_input_parameters: bool = False) -> str: """Converts this OpenPulse problem into IR representation. + Args: + sort_input_parameters (bool): whether input parameters should be printed + in a sorted order. Defaults to False. + Returns: str: a str representing the OpenPulse program encoding the PulseSequence. """ program = deepcopy(self._program) program.autodeclare(encal=False) - for param in self.parameters: + parameters = ( + sorted(self.parameters, key=lambda p: p.name, reverse=True) + if sort_input_parameters + else self.parameters + ) + for param in parameters: program.declare(param._to_oqpy_expression(), to_beginning=True) if self._capture_v0_count: @@ -420,10 +429,11 @@ def __call__(self, arg: Any | None = None, **kwargs) -> PulseSequence: return self.make_bound_pulse_sequence(param_values) def __eq__(self, other): + sort_input_parameters = True return ( isinstance(other, PulseSequence) and self.parameters == other.parameters - and self.to_ir() == other.to_ir() + and self.to_ir(sort_input_parameters) == other.to_ir(sort_input_parameters) ) From b16cf753f0d82454e255067cfa70cdaca52f4020 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 14 Feb 2024 16:15:48 +0000 Subject: [PATCH 057/347] prepare release v1.70.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3682e0212..6dd9c3495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.70.2 (2024-02-14) + +### Bug Fixes and Other Changes + + * Sort input parameters when doing testing equality of two PulseSequences + ## v1.70.1 (2024-02-13) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9ed071fb1..eb0e14a0b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.2.dev0" +__version__ = "1.70.2" From d53bcb5f19d6a4d0690357394ed58691bd000e2a Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 14 Feb 2024 16:15:48 +0000 Subject: [PATCH 058/347] update development version to v1.70.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index eb0e14a0b..af639799b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.2" +__version__ = "1.70.3.dev0" From e11bb310aa146df658d2fc9e6080f0654067889d Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:22:58 -0800 Subject: [PATCH 059/347] infra: update docstrings to be ruff compatible (#778) --- .github/workflows/check-format.yml | 3 +- pydoclint-baseline.txt | 233 ++++++++++++++++++ setup.cfg | 3 - src/braket/_sdk/_version.py | 2 +- .../ahs/analog_hamiltonian_simulation.py | 7 +- src/braket/ahs/atom_arrangement.py | 6 +- src/braket/ahs/driving_field.py | 7 +- src/braket/ahs/pattern.py | 3 +- src/braket/ahs/shifting_field.py | 9 +- src/braket/annealing/problem.py | 31 ++- src/braket/aws/aws_device.py | 89 ++++--- src/braket/aws/aws_quantum_job.py | 32 ++- src/braket/aws/aws_quantum_task.py | 76 +++--- src/braket/aws/aws_quantum_task_batch.py | 22 +- src/braket/aws/aws_session.py | 100 ++++---- src/braket/aws/queue_information.py | 12 +- src/braket/circuits/angled_gate.py | 101 ++++---- src/braket/circuits/ascii_circuit_diagram.py | 28 +-- src/braket/circuits/basis_state.py | 8 +- src/braket/circuits/braket_program_context.py | 6 +- src/braket/circuits/circuit.py | 96 +++----- src/braket/circuits/circuit_diagram.py | 5 +- src/braket/circuits/circuit_helpers.py | 5 +- src/braket/circuits/compiler_directive.py | 5 +- src/braket/circuits/compiler_directives.py | 6 +- src/braket/circuits/gate.py | 17 +- src/braket/circuits/gate_calibrations.py | 34 ++- src/braket/circuits/gates.py | 44 ++-- src/braket/circuits/instruction.py | 32 +-- src/braket/circuits/moments.py | 37 ++- src/braket/circuits/noise.py | 176 ++++++------- src/braket/circuits/noise_helpers.py | 38 +-- .../circuit_instruction_criteria.py | 6 +- src/braket/circuits/noise_model/criteria.py | 11 +- .../noise_model/criteria_input_parsing.py | 8 +- .../circuits/noise_model/gate_criteria.py | 10 +- .../noise_model/initialization_criteria.py | 7 +- .../circuits/noise_model/noise_model.py | 37 ++- .../noise_model/observable_criteria.py | 11 +- .../qubit_initialization_criteria.py | 16 +- .../noise_model/result_type_criteria.py | 1 + .../noise_model/unitary_gate_criteria.py | 13 +- src/braket/circuits/noises.py | 151 ++++++------ src/braket/circuits/observable.py | 30 +-- src/braket/circuits/observables.py | 68 ++--- src/braket/circuits/operator.py | 4 +- src/braket/circuits/quantum_operator.py | 35 ++- .../circuits/quantum_operator_helpers.py | 26 +- src/braket/circuits/result_type.py | 72 +++--- src/braket/circuits/result_types.py | 53 ++-- src/braket/circuits/serialization.py | 7 +- src/braket/circuits/translations.py | 27 +- src/braket/circuits/unitary_calculation.py | 3 +- src/braket/devices/device.py | 13 +- src/braket/devices/local_simulator.py | 22 +- src/braket/error_mitigation/debias.py | 4 +- .../error_mitigation/error_mitigation.py | 7 +- src/braket/ipython_utils.py | 3 +- src/braket/jobs/config.py | 4 +- src/braket/jobs/data_persistence.py | 21 +- src/braket/jobs/environment_variables.py | 20 +- src/braket/jobs/hybrid_job.py | 16 +- src/braket/jobs/image_uris.py | 19 +- src/braket/jobs/local/local_job.py | 30 ++- src/braket/jobs/local/local_job_container.py | 31 ++- .../jobs/local/local_job_container_setup.py | 51 ++-- src/braket/jobs/logs.py | 42 ++-- src/braket/jobs/metrics.py | 14 +- .../cwl_insights_metrics_fetcher.py | 51 ++-- .../jobs/metrics_data/cwl_metrics_fetcher.py | 28 +-- .../jobs/metrics_data/log_metrics_parser.py | 47 ++-- src/braket/jobs/quantum_job.py | 18 +- src/braket/jobs/quantum_job_creation.py | 64 ++--- src/braket/jobs/serialization.py | 24 +- src/braket/parametric/free_parameter.py | 18 +- .../parametric/free_parameter_expression.py | 42 ++-- src/braket/parametric/parameterizable.py | 11 +- src/braket/pulse/ast/approximation_parser.py | 117 +++++++-- src/braket/pulse/ast/qasm_parser.py | 3 +- src/braket/pulse/ast/qasm_transformer.py | 5 +- src/braket/pulse/frame.py | 13 +- src/braket/pulse/port.py | 10 +- src/braket/pulse/pulse_sequence.py | 67 +++-- src/braket/pulse/waveforms.py | 92 ++++--- .../quantum_information/pauli_string.py | 14 +- src/braket/registers/qubit.py | 15 +- src/braket/registers/qubit_set.py | 13 +- src/braket/tasks/__init__.py | 2 +- ...iltonian_simulation_quantum_task_result.py | 10 +- .../tasks/annealing_quantum_task_result.py | 63 +++-- .../tasks/gate_model_quantum_task_result.py | 41 ++- src/braket/tasks/local_quantum_task.py | 16 +- .../photonic_model_quantum_task_result.py | 5 +- src/braket/tasks/quantum_task.py | 17 +- src/braket/tasks/quantum_task_batch.py | 7 +- src/braket/timings/time_series.py | 36 ++- src/braket/tracking/pricing.py | 8 +- src/braket/tracking/tracker.py | 21 +- src/braket/tracking/tracking_context.py | 10 +- .../gate_model_device_testing_utils.py | 10 +- .../unit_tests/braket/circuits/test_noises.py | 2 +- .../braket/devices/test_local_simulator.py | 2 +- 102 files changed, 1714 insertions(+), 1354 deletions(-) create mode 100644 pydoclint-baseline.txt diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 1795135e7..8f6807b5e 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -23,8 +23,7 @@ jobs: python-version: '3.9' - name: Install dependencies run: | - pip install --upgrade pip - pip install -e .[test] + pip install tox - name: Run code format checks run: | tox -e linters_check diff --git a/pydoclint-baseline.txt b/pydoclint-baseline.txt new file mode 100644 index 000000000..816c4265a --- /dev/null +++ b/pydoclint-baseline.txt @@ -0,0 +1,233 @@ +src/braket/aws/aws_device.py + DOC101: Method `AwsDevice.run_batch`: Docstring contains fewer arguments than in function signature. + DOC103: Method `AwsDevice.run_batch`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**aws_quantum_task_kwargs: , *aws_quantum_task_args: ]. +-------------------- +src/braket/aws/aws_quantum_job.py + DOC502: Method `AwsQuantumJob.create` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC101: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: Docstring contains fewer arguments than in function signature. + DOC109: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [aws_session: AwsSession, job_arn: str]. + DOC502: Method `AwsQuantumJob.logs` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC502: Method `AwsQuantumJob.cancel` has a "Raises" section in the docstring, but there are not "raise" statements in the body +-------------------- +src/braket/aws/aws_quantum_task.py + DOC101: Method `AwsQuantumTask.create`: Docstring contains fewer arguments than in function signature. + DOC103: Method `AwsQuantumTask.create`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. + DOC501: Method `AwsQuantumTask.create` has "raise" statements, but the docstring does not have a "Raises" section + DOC101: Method `AwsQuantumTask._aws_session_for_task_arn`: Docstring contains fewer arguments than in function signature. + DOC109: Method `AwsQuantumTask._aws_session_for_task_arn`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AwsQuantumTask._aws_session_for_task_arn`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [task_arn: str]. + DOC501: Function `_create_annealing_device_params` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/aws/aws_quantum_task_batch.py + DOC501: Method `AwsQuantumTaskBatch.results` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `AwsQuantumTaskBatch.retry_unsuccessful_tasks` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/aws/aws_session.py + DOC106: Method `AwsSession.create_quantum_task`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `AwsSession.create_quantum_task`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC106: Method `AwsSession.create_job`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `AwsSession.create_job`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC001: Function/method `parse_s3_uri`: Potential formatting errors in docstring. Error message: Expected a colon in 'a valid S3 URI.'. + DOC101: Method `AwsSession.parse_s3_uri`: Docstring contains fewer arguments than in function signature. + DOC109: Method `AwsSession.parse_s3_uri`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AwsSession.parse_s3_uri`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [s3_uri: str]. + DOC201: Method `AwsSession.parse_s3_uri` does not have a return section in docstring + DOC203: Method `AwsSession.parse_s3_uri` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). + DOC501: Method `AwsSession.parse_s3_uri` has "raise" statements, but the docstring does not have a "Raises" section + DOC001: Function/method `construct_s3_uri`: Potential formatting errors in docstring. Error message: Expected a colon in 'valid to generate an S3 URI'. + DOC101: Method `AwsSession.construct_s3_uri`: Docstring contains fewer arguments than in function signature. + DOC109: Method `AwsSession.construct_s3_uri`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AwsSession.construct_s3_uri`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [*dirs: str, bucket: str]. + DOC201: Method `AwsSession.construct_s3_uri` does not have a return section in docstring + DOC203: Method `AwsSession.construct_s3_uri` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). + DOC501: Method `AwsSession.construct_s3_uri` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `AwsSession.get_full_image_tag` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/angled_gate.py + DOC101: Method `AngledGate.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `AngledGate.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `AngledGate.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AngledGate.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC501: Method `DoubleAngledGate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `TripleAngledGate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/braket_program_context.py + DOC101: Method `BraketProgramContext.add_gate_instruction`: Docstring contains fewer arguments than in function signature. + DOC103: Method `BraketProgramContext.add_gate_instruction`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [*params: ]. +-------------------- +src/braket/circuits/circuit.py + DOC101: Method `Circuit.__init__`: Docstring contains fewer arguments than in function signature. + DOC103: Method `Circuit.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. + DOC502: Method `Circuit.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC105: Method `Circuit.apply_gate_noise`: Argument names match, but type hints do not match + DOC001: Function/method `_validate_parameters`: Potential formatting errors in docstring. Error message: No specification for "Raises": "" + DOC101: Method `Circuit._validate_parameters`: Docstring contains fewer arguments than in function signature. + DOC109: Method `Circuit._validate_parameters`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Circuit._validate_parameters`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [parameter_values: dict[str, Number]]. + DOC501: Method `Circuit._validate_parameters` has "raise" statements, but the docstring does not have a "Raises" section + DOC101: Method `Circuit.add`: Docstring contains fewer arguments than in function signature. + DOC103: Method `Circuit.add`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. + DOC502: Method `Circuit.to_unitary` has a "Raises" section in the docstring, but there are not "raise" statements in the body +-------------------- +src/braket/circuits/compiler_directive.py + DOC501: Method `CompilerDirective.__init__` has "raise" statements, but the docstring does not have a "Raises" section + DOC101: Method `CompilerDirective.to_ir`: Docstring contains fewer arguments than in function signature. + DOC103: Method `CompilerDirective.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC501: Method `CompilerDirective.counterpart` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/gate.py + DOC502: Method `Gate.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC501: Method `Gate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section + DOC001: Function/method `to_ir`: Potential formatting errors in docstring. Error message: Expected a colon in "properties don't correspond to the `ir_type`.". + DOC101: Method `Gate.to_ir`: Docstring contains fewer arguments than in function signature. + DOC109: Method `Gate.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Gate.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [control: Optional[QubitSet], control_state: Optional[BasisStateInput], ir_type: IRType, power: float, serialization_properties: Optional[SerializationProperties], target: QubitSet]. + DOC201: Method `Gate.to_ir` does not have a return section in docstring + DOC203: Method `Gate.to_ir` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). + DOC501: Method `Gate.to_ir` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Gate._to_jaqcd` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/gates.py + DOC105: Method `Unitary.__init__`: Argument names match, but type hints do not match + DOC105: Method `Unitary.unitary`: Argument names match, but type hints do not match + DOC501: Method `PulseGate.__init__` has "raise" statements, but the docstring does not have a "Raises" section + DOC101: Method `PulseGate.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `PulseGate.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `PulseGate.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `PulseGate.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. +-------------------- +src/braket/circuits/noise.py + DOC502: Method `Noise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC001: Function/method `to_ir`: Potential formatting errors in docstring. Error message: Expected a colon in "properties don't correspond to the `ir_type`.". + DOC101: Method `Noise.to_ir`: Docstring contains fewer arguments than in function signature. + DOC109: Method `Noise.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Noise.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [ir_type: IRType, serialization_properties: SerializationProperties | None, target: QubitSet]. + DOC201: Method `Noise.to_ir` does not have a return section in docstring + DOC203: Method `Noise.to_ir` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). + DOC501: Method `Noise.to_ir` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Noise._to_jaqcd` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Noise._to_openqasm` has "raise" statements, but the docstring does not have a "Raises" section + DOC101: Method `Noise.to_matrix`: Docstring contains fewer arguments than in function signature. + DOC106: Method `Noise.to_matrix`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `Noise.to_matrix`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Noise.to_matrix`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. + DOC203: Method `Noise.to_matrix` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Iterable[np.ndarray]']; docstring return section types: ['Iterable[ndarray]'] + DOC501: Method `Noise.to_matrix` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Noise.from_dict` has "raise" statements, but the docstring does not have a "Raises" section + DOC502: Method `SingleProbabilisticNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC101: Method `SingleProbabilisticNoise.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `SingleProbabilisticNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `SingleProbabilisticNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `SingleProbabilisticNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC502: Method `SingleProbabilisticNoise_34.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC502: Method `SingleProbabilisticNoise_1516.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC101: Method `MultiQubitPauliNoise.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `MultiQubitPauliNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `MultiQubitPauliNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `MultiQubitPauliNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC203: Method `PauliNoise.probX` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] + DOC203: Method `PauliNoise.probY` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] + DOC203: Method `PauliNoise.probZ` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] + DOC101: Method `PauliNoise.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `PauliNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `PauliNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `PauliNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC502: Method `DampingNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC101: Method `DampingNoise.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `DampingNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `DampingNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `DampingNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC502: Method `GeneralizedAmplitudeDampingNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC501: Function `_validate_param_value` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/noise_helpers.py + DOC501: Function `check_noise_target_gates` has "raise" statements, but the docstring does not have a "Raises" section + DOC105: Function `check_noise_target_unitary`: Argument names match, but type hints do not match + DOC501: Function `check_noise_target_unitary` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Function `check_noise_target_qubits` has "raise" statements, but the docstring does not have a "Raises" section + DOC105: Function `apply_noise_to_gates`: Argument names match, but type hints do not match + DOC502: Function `apply_noise_to_gates` has a "Raises" section in the docstring, but there are not "raise" statements in the body +-------------------- +src/braket/circuits/noise_model/criteria.py + DOC501: Method `Criteria.applicable_key_types` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Criteria.get_keys` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Criteria.to_dict` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Criteria.from_dict` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/noise_model/criteria_input_parsing.py + DOC501: Function `parse_operator_input` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Function `parse_qubit_input` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/noise_model/initialization_criteria.py + DOC501: Method `InitializationCriteria.qubit_intersection` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/noise_model/result_type_criteria.py + DOC501: Method `ResultTypeCriteria.result_type_matches` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/noises.py + DOC101: Method `PauliChannel.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `PauliChannel.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `PauliChannel.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `PauliChannel.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `Depolarizing.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `Depolarizing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `Depolarizing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Depolarizing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `TwoQubitDepolarizing.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `TwoQubitDepolarizing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `TwoQubitDepolarizing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `TwoQubitDepolarizing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `TwoQubitDephasing.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `TwoQubitDephasing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `TwoQubitDephasing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `TwoQubitDephasing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `TwoQubitPauliChannel.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `TwoQubitPauliChannel.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `TwoQubitPauliChannel.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `TwoQubitPauliChannel.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `AmplitudeDamping.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `AmplitudeDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `AmplitudeDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `AmplitudeDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `GeneralizedAmplitudeDamping.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `GeneralizedAmplitudeDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `GeneralizedAmplitudeDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `GeneralizedAmplitudeDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC101: Method `PhaseDamping.bind_values`: Docstring contains fewer arguments than in function signature. + DOC106: Method `PhaseDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `PhaseDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `PhaseDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. + DOC501: Method `Kraus.kraus` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Kraus.to_dict` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Kraus.from_dict` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/observable.py + DOC501: Method `Observable._to_openqasm` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Observable.basis_rotation_gates` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Observable.eigenvalues` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `Observable.eigenvalue` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/observables.py + DOC501: Method `TensorProduct.__init__` has "raise" statements, but the docstring does not have a "Raises" section + DOC501: Method `TensorProduct.eigenvalue` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/circuits/operator.py + DOC101: Method `Operator.to_ir`: Docstring contains fewer arguments than in function signature. + DOC106: Method `Operator.to_ir`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature + DOC109: Method `Operator.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list + DOC103: Method `Operator.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. +-------------------- +src/braket/circuits/result_type.py + DOC101: Method `ResultType.to_ir`: Docstring contains fewer arguments than in function signature. + DOC103: Method `ResultType.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. +-------------------- +src/braket/devices/local_simulator.py + DOC101: Method `LocalSimulator.run_batch`: Docstring contains fewer arguments than in function signature. + DOC103: Method `LocalSimulator.run_batch`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. + DOC501: Method `LocalSimulator.run_batch` has "raise" statements, but the docstring does not have a "Raises" section +-------------------- +src/braket/tasks/gate_model_quantum_task_result.py + DOC502: Method `GateModelQuantumTaskResult.from_object` has a "Raises" section in the docstring, but there are not "raise" statements in the body + DOC502: Method `GateModelQuantumTaskResult.from_string` has a "Raises" section in the docstring, but there are not "raise" statements in the body +-------------------- diff --git a/setup.cfg b/setup.cfg index 3bad103a3..d9dbb5b62 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,6 +39,3 @@ exclude = bin build venv -rst-roles = - # Python programming language: - py:func,py:mod,mod diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index af639799b..fc41f12eb 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. """Version information. - Version number (major.minor.patch[-label]) +Version number (major.minor.patch[-label]) """ __version__ = "1.70.3.dev0" diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index b02091d4f..8d8cce10f 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -54,7 +54,7 @@ def to_ir(self) -> ir.Program: representation. Returns: - Program: A representation of the circuit in the IR format. + ir.Program: A representation of the circuit in the IR format. """ return ir.Program( setup=ir.Setup(ahs_register=self._register_to_ir()), @@ -77,7 +77,7 @@ def _hamiltonian_to_ir(self) -> ir.Hamiltonian: shiftingFields=terms[AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY], ) - def discretize(self, device) -> AnalogHamiltonianSimulation: # noqa + def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: # noqa """Creates a new AnalogHamiltonianSimulation with all numerical values represented as Decimal objects with fixed precision based on the capabilities of the device. @@ -88,9 +88,8 @@ def discretize(self, device) -> AnalogHamiltonianSimulation: # noqa AnalogHamiltonianSimulation: A discretized version of this program. Raises: - DiscretizeError: If unable to discretize the program. + DiscretizationError: If unable to discretize the program. """ - required_action_schema = DeviceActionType.AHS if (required_action_schema not in device.properties.action) or ( device.properties.action[required_action_schema].actionType != required_action_schema diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py index bb7088347..1d47a66b8 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -73,6 +73,7 @@ def add( atom (in meters). The coordinates can be a numpy array of shape (2,) or a tuple of int, float, Decimal site_type (SiteType): The type of site. Optional. Default is FILLED. + Returns: AtomArrangement: returns self (to allow for chaining). """ @@ -109,6 +110,9 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: properties (DiscretizationProperties): Capabilities of a device that represent the resolution with which the device can implement the parameters. + Raises: + DiscretizationError: If unable to discretize the program. + Returns: AtomArrangement: A new discretized atom arrangement. """ @@ -117,7 +121,7 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: discretized_arrangement = AtomArrangement() for site in self._sites: new_coordinates = tuple( - (round(Decimal(c) / position_res) * position_res for c in site.coordinate) + round(Decimal(c) / position_res) * position_res for c in site.coordinate ) discretized_arrangement.add(new_coordinates, site.site_type) return discretized_arrangement diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index 02c8bd276..f6c6430f2 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -104,7 +104,6 @@ def stitch( Returns: DrivingField: The stitched DrivingField object. """ - amplitude = self.amplitude.time_series.stitch(other.amplitude.time_series, boundary) detuning = self.detuning.time_series.stitch(other.detuning.time_series, boundary) phase = self.phase.time_series.stitch(other.phase.time_series, boundary) @@ -143,8 +142,7 @@ def discretize(self, properties: DiscretizationProperties) -> DrivingField: def from_lists( times: list[float], amplitudes: list[float], detunings: list[float], phases: list[float] ) -> DrivingField: - """ - Builds DrivingField Hamiltonian from lists defining time evolution + """Builds DrivingField Hamiltonian from lists defining time evolution of Hamiltonian parameters (Rabi frequency, detuning, phase). The values of the parameters at each time points are global for all atoms. @@ -154,6 +152,9 @@ def from_lists( detunings (list[float]): The values of the detuning phases (list[float]): The values of the phase + Raises: + ValueError: If any of the input args length is different from the rest. + Returns: DrivingField: DrivingField Hamiltonian. """ diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py index 17e40a36f..462f0e369 100644 --- a/src/braket/ahs/pattern.py +++ b/src/braket/ahs/pattern.py @@ -30,7 +30,8 @@ def __init__(self, series: list[Number]): @property def series(self) -> list[Number]: """list[Number]: A series of numbers representing the local - pattern of real numbers.""" + pattern of real numbers. + """ return self._series def discretize(self, resolution: Decimal) -> Pattern: diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index 846d7ad2e..7bed1fe8a 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -56,7 +56,8 @@ def terms(self) -> list[Hamiltonian]: def magnitude(self) -> Field: r"""Field: containing the global magnitude time series :math:`\Delta(t)`, where time is measured in seconds (s) and values measured in rad/s) - and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1.""" + and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. + """ return self._magnitude @staticmethod @@ -68,6 +69,9 @@ def from_lists(times: list[float], values: list[float], pattern: list[float]) -> values (list[float]): The values of the shifting field pattern (list[float]): The pattern of the shifting field + Raises: + ValueError: If the length of times and values differs. + Returns: ShiftingField: The shifting field obtained """ @@ -99,6 +103,9 @@ def stitch( - "left" - use the last value from the left time series as the boundary point. - "right" - use the first value from the right time series as the boundary point. + Raises: + ValueError: The ShiftingField patterns differ. + Returns: ShiftingField: The stitched ShiftingField object. diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index d8b40c372..d55de4e6e 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -14,7 +14,6 @@ from __future__ import annotations from enum import Enum -from typing import Dict, Tuple import braket.ir.annealing as ir @@ -37,16 +36,16 @@ class Problem: def __init__( self, problem_type: ProblemType, - linear: Dict[int, float] | None = None, - quadratic: Dict[Tuple[int, int], float] | None = None, + linear: dict[int, float] | None = None, + quadratic: dict[tuple[int, int], float] | None = None, ): - """ + """Initialzes a `Problem`. Args: problem_type (ProblemType): The type of annealing problem - linear (Dict[int, float] | None): The linear terms of this problem, + linear (dict[int, float] | None): The linear terms of this problem, as a map of variable to coefficient - quadratic (Dict[Tuple[int, int], float] | None): The quadratic terms of this problem, + quadratic (dict[tuple[int, int], float] | None): The quadratic terms of this problem, as a map of variables to coefficient Examples: @@ -71,20 +70,20 @@ def problem_type(self) -> ProblemType: return self._problem_type @property - def linear(self) -> Dict[int, float]: + def linear(self) -> dict[int, float]: """The linear terms of this problem. Returns: - Dict[int, float]: The linear terms of this problem, as a map of variable to coefficient + dict[int, float]: The linear terms of this problem, as a map of variable to coefficient """ return self._linear @property - def quadratic(self) -> Dict[Tuple[int, int], float]: + def quadratic(self) -> dict[tuple[int, int], float]: """The quadratic terms of this problem. Returns: - Dict[Tuple[int, int], float]: The quadratic terms of this problem, + dict[tuple[int, int], float]: The quadratic terms of this problem, as a map of variables to coefficient """ return self._quadratic @@ -102,11 +101,11 @@ def add_linear_term(self, term: int, coefficient: float) -> Problem: self._linear[term] = coefficient return self - def add_linear_terms(self, coefficients: Dict[int, float]) -> Problem: + def add_linear_terms(self, coefficients: dict[int, float]) -> Problem: """Adds linear terms to the problem. Args: - coefficients (Dict[int, float]): A map of variable to coefficient + coefficients (dict[int, float]): A map of variable to coefficient Returns: Problem: This problem object @@ -114,11 +113,11 @@ def add_linear_terms(self, coefficients: Dict[int, float]) -> Problem: self._linear.update(coefficients) return self - def add_quadratic_term(self, term: Tuple[int, int], coefficient: float) -> Problem: + def add_quadratic_term(self, term: tuple[int, int], coefficient: float) -> Problem: """Adds a quadratic term to the problem. Args: - term (Tuple[int, int]): The variables of the quadratic term + term (tuple[int, int]): The variables of the quadratic term coefficient (float): The coefficient of the quadratic term Returns: @@ -127,11 +126,11 @@ def add_quadratic_term(self, term: Tuple[int, int], coefficient: float) -> Probl self._quadratic[term] = coefficient return self - def add_quadratic_terms(self, coefficients: Dict[Tuple[int, int], float]) -> Problem: + def add_quadratic_terms(self, coefficients: dict[tuple[int, int], float]) -> Problem: """Adds quadratic terms to the problem. Args: - coefficients (Dict[Tuple[int, int], float]): A map of variables to coefficient + coefficients (dict[tuple[int, int], float]): A map of variables to coefficient Returns: Problem: This problem object diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index ee9c4128b..43780aa38 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -20,7 +20,7 @@ import warnings from datetime import datetime from enum import Enum -from typing import Optional, Union +from typing import Any, ClassVar, Optional, Union from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -36,9 +36,9 @@ from braket.circuits.noise_model import NoiseModel from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties from braket.device_schema.dwave import DwaveProviderProperties -from braket.device_schema.pulse.pulse_device_action_properties_v1 import ( # noqa TODO: Remove device_action module once this is added to init in the schemas repo - PulseDeviceActionProperties, -) + +# TODO: Remove device_action module once this is added to init in the schemas repo +from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties from braket.devices.device import Device from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram @@ -57,8 +57,7 @@ class AwsDeviceType(str, Enum): class AwsDevice(Device): - """ - Amazon Braket implementation of a device. + """Amazon Braket implementation of a device. Use this class to retrieve the latest metadata about the device and to run a quantum task on the device. """ @@ -71,7 +70,7 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) - _RIGETTI_GATES_TO_BRAKET = { + _RIGETTI_GATES_TO_BRAKET: ClassVar[dict[str, str | None]] = { # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. "Rx_12": None, "Cz": "CZ", @@ -85,7 +84,8 @@ def __init__( aws_session: Optional[AwsSession] = None, noise_model: Optional[NoiseModel] = None, ): - """ + """Initializes an `AwsDevice`. + Args: arn (str): The ARN of the device aws_session (Optional[AwsSession]): An AWS session object. Default is `None`. @@ -134,15 +134,14 @@ def run( inputs: Optional[dict[str, float]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, reservation_arn: str | None = None, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, + *aws_quantum_task_args: Any, + **aws_quantum_task_kwargs: Any, ) -> AwsQuantumTask: - """ - Run a quantum task specification on this device. A quantum task can be a circuit or an + """Run a quantum task specification on this device. A quantum task can be a circuit or an annealing problem. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa + task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): Specification of quantum task (circuit, OpenQASM program or AHS program) to run on device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to @@ -168,6 +167,8 @@ def run( Note: If you are creating tasks in a job that itself was created reservation ARN, those tasks do not need to be created with the reservation ARN. Default: None. + *aws_quantum_task_args (Any): Arbitrary arguments. + **aws_quantum_task_kwargs (Any): Arbitrary keyword arguments. Returns: AwsQuantumTask: An AwsQuantumTask that tracks the execution on the device. @@ -200,7 +201,7 @@ def run( See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` - """ + """ # noqa E501 if self._noise_model: task_specification = self._apply_noise_model_to_circuit(task_specification) return AwsQuantumTask.create( @@ -297,7 +298,7 @@ def run_batch( See Also: `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` - """ + """ # noqa E501 if self._noise_model: task_specifications = [ self._apply_noise_model_to_circuit(task_specification) @@ -327,9 +328,7 @@ def run_batch( ) def refresh_metadata(self) -> None: - """ - Refresh the `AwsDevice` object with the most recent Device metadata. - """ + """Refresh the `AwsDevice` object with the most recent Device metadata.""" self._populate_properties(self._aws_session) def _get_session_and_initialize(self, session: AwsSession) -> AwsSession: @@ -417,8 +416,7 @@ def arn(self) -> str: @property def gate_calibrations(self) -> Optional[GateCalibrations]: - """ - Calibration data for a QPU. Calibration data is shown for gates on particular gubits. + """Calibration data for a QPU. Calibration data is shown for gates on particular gubits. If a QPU does not expose these calibrations, None is returned. Returns: @@ -432,6 +430,7 @@ def gate_calibrations(self) -> Optional[GateCalibrations]: @property def is_available(self) -> bool: """Returns true if the device is currently available. + Returns: bool: Return if the device is currently available. """ @@ -493,7 +492,8 @@ def properties(self) -> DeviceCapabilities: Please see `braket.device_schema` in amazon-braket-schemas-python_ - .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python""" + .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python + """ return self._properties @property @@ -519,8 +519,7 @@ def topology_graph(self) -> DiGraph: return self._topology_graph def _construct_topology_graph(self) -> DiGraph: - """ - Construct topology graph. If no such metadata is available, return `None`. + """Construct topology graph. If no such metadata is available, return `None`. Returns: DiGraph: topology of QPU as a networkx `DiGraph` object. @@ -557,9 +556,9 @@ def _default_max_parallel(self) -> int: return AwsDevice.DEFAULT_MAX_PARALLEL def __repr__(self): - return "Device('name': {}, 'arn': {})".format(self.name, self.arn) + return f"Device('name': {self.name}, 'arn': {self.arn})" - def __eq__(self, other): + def __eq__(self, other: AwsDevice): if isinstance(other, AwsDevice): return self.arn == other.arn return NotImplemented @@ -567,16 +566,18 @@ def __eq__(self, other): @property def frames(self) -> dict[str, Frame]: """Returns a dict mapping frame ids to the frame objects for predefined frames - for this device.""" + for this device. + """ self._update_pulse_properties() - return self._frames or dict() + return self._frames or {} @property def ports(self) -> dict[str, Port]: """Returns a dict mapping port ids to the port objects for predefined ports - for this device.""" + for this device. + """ self._update_pulse_properties() - return self._ports or dict() + return self._ports or {} @staticmethod def get_devices( @@ -588,8 +589,7 @@ def get_devices( order_by: str = "name", aws_session: Optional[AwsSession] = None, ) -> list[AwsDevice]: - """ - Get devices based on filters and desired ordering. The result is the AND of + """Get devices based on filters and desired ordering. The result is the AND of all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Examples: @@ -612,17 +612,17 @@ def get_devices( aws_session (Optional[AwsSession]): An AWS session object. Default is `None`. + Raises: + ValueError: order_by not in ['arn', 'name', 'type', 'provider_name', 'status'] + Returns: list[AwsDevice]: list of AWS devices """ - if order_by not in AwsDevice._GET_DEVICES_ORDER_BY_KEYS: raise ValueError( f"order_by '{order_by}' must be in {AwsDevice._GET_DEVICES_ORDER_BY_KEYS}" ) - types = ( - frozenset(types) if types else frozenset({device_type for device_type in AwsDeviceType}) - ) + types = frozenset(types or AwsDeviceType) aws_session = aws_session if aws_session else AwsSession() device_map = {} session_region = aws_session.boto_session.region_name @@ -662,7 +662,8 @@ def get_devices( warnings.warn( f"{error_code}: Unable to search region '{region}' for devices." " Please check your settings or try again later." - f" Continuing without devices in '{region}'." + f" Continuing without devices in '{region}'.", + stacklevel=1, ) devices = list(device_map.values()) @@ -674,14 +675,14 @@ def _update_pulse_properties(self) -> None: self.properties.pulse, PulseDeviceActionProperties ): if self._ports is None: - self._ports = dict() + self._ports = {} port_data = self.properties.pulse.ports for port_id, port in port_data.items(): self._ports[port_id] = Port( port_id=port_id, dt=port.dt, properties=json.loads(port.json()) ) if self._frames is None: - self._frames = dict() + self._frames = {} frame_data = self.properties.pulse.frames if frame_data: for frame_id, frame in frame_data.items(): @@ -697,6 +698,7 @@ def _update_pulse_properties(self) -> None: @staticmethod def get_device_region(device_arn: str) -> str: """Gets the region from a device arn. + Args: device_arn (str): The device ARN. @@ -715,8 +717,7 @@ def get_device_region(device_arn: str) -> str: ) def queue_depth(self) -> QueueDepthInfo: - """ - Task queue depth refers to the total number of quantum tasks currently waiting + """Task queue depth refers to the total number of quantum tasks currently waiting to run on a particular device. Returns: @@ -763,8 +764,7 @@ def queue_depth(self) -> QueueDepthInfo: return QueueDepthInfo(**queue_info) def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: - """ - Refreshes the gate calibration data upon request. + """Refreshes the gate calibration data upon request. If the device does not have calibration data, None is returned. @@ -796,7 +796,7 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: return None def _parse_waveforms(self, waveforms_json: dict) -> dict: - waveforms = dict() + waveforms = {} for waveform in waveforms_json: parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) waveforms[parsed_waveform.id] = parsed_waveform @@ -810,8 +810,7 @@ def _parse_pulse_sequence( def _parse_calibration_json( self, calibration_data: dict ) -> dict[tuple[Gate, QubitSet], PulseSequence]: - """ - Takes the json string from the device calibration URL and returns a structured dictionary of + """Takes the json string from the device calibration URL and returns a structured dictionary of corresponding `dict[tuple[Gate, QubitSet], PulseSequence]` to represent the calibration data. Args: diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 31347311e..711aeab1b 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -20,7 +20,7 @@ from enum import Enum from logging import Logger, getLogger from pathlib import Path -from typing import Any +from typing import Any, ClassVar import boto3 from botocore.exceptions import ClientError @@ -49,12 +49,14 @@ class AwsQuantumJob(QuantumJob): """Amazon Braket implementation of a quantum job.""" - TERMINAL_STATES = {"CANCELLED", "COMPLETED", "FAILED"} + TERMINAL_STATES: ClassVar[set[str]] = {"CANCELLED", "COMPLETED", "FAILED"} RESULTS_FILENAME = "results.json" RESULTS_TAR_FILENAME = "model.tar.gz" LOG_GROUP = "/aws/braket/jobs" class LogState(Enum): + """Log state enum.""" + TAILING = "tailing" JOB_COMPLETE = "job_complete" COMPLETE = "complete" @@ -223,7 +225,8 @@ def create( return job def __init__(self, arn: str, aws_session: AwsSession | None = None, quiet: bool = False): - """ + """Initializes an `AwsQuantumJob`. + Args: arn (str): The ARN of the hybrid job. aws_session (AwsSession | None): The `AwsSession` for connecting to AWS services. @@ -231,6 +234,9 @@ def __init__(self, arn: str, aws_session: AwsSession | None = None, quiet: bool region of the hybrid job. quiet (bool): Sets the verbosity of the logger to low and does not report queue position. Default is `False`. + + Raises: + ValueError: Supplied region and session region do not match. """ self._arn: str = arn self._quiet = quiet @@ -246,8 +252,11 @@ def __init__(self, arn: str, aws_session: AwsSession | None = None, quiet: bool @staticmethod def _is_valid_aws_session_region_for_job_arn(aws_session: AwsSession, job_arn: str) -> bool: - """ - bool: `True` when the aws_session region matches the job_arn region; otherwise `False`. + """Checks whether the job region and session region match. + + Returns: + bool: `True` when the aws_session region matches the job_arn region; otherwise + `False`. """ job_region = job_arn.split(":")[3] return job_region == aws_session.region @@ -285,6 +294,7 @@ def state(self, use_cached_value: bool = False) -> str: value from the Amazon Braket `GetJob` operation. If `False`, calls the `GetJob` operation to retrieve metadata, which also updates the cached value. Default = `False`. + Returns: str: The value of `status` in `metadata()`. This is the value of the `status` key in the Amazon Braket `GetJob` operation. @@ -295,8 +305,7 @@ def state(self, use_cached_value: bool = False) -> str: return self.metadata(use_cached_value).get("status") def queue_position(self) -> HybridJobQueueInfo: - """ - The queue position details for the hybrid job. + """The queue position details for the hybrid job. Returns: HybridJobQueueInfo: Instance of HybridJobQueueInfo class representing @@ -413,6 +422,7 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: from the Amazon Braket `GetJob` operation, if it exists; if does not exist, `GetJob` is called to retrieve the metadata. If `False`, always calls `GetJob`, which also updates the cached value. Default: `False`. + Returns: dict[str, Any]: Dict that specifies the hybrid job metadata defined in Amazon Braket. """ @@ -441,7 +451,7 @@ def metrics( when there is a conflict. Default: MetricStatistic.MAX. Returns: - dict[str, list[Any]] : The metrics data. + dict[str, list[Any]]: The metrics data. """ fetcher = CwlInsightsMetricsFetcher(self._aws_session) metadata = self.metadata(True) @@ -471,7 +481,7 @@ def result( poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> dict[str, Any]: - """Retrieves the hybrid job result persisted using save_job_result() function. + """Retrieves the hybrid job result persisted using the `save_job_result` function. Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. @@ -486,7 +496,6 @@ def result( RuntimeError: if hybrid job is in a FAILED or CANCELLED state. TimeoutError: if hybrid job execution exceeds the polling timeout period. """ - with tempfile.TemporaryDirectory() as temp_dir: job_name = self.metadata(True)["jobName"] @@ -526,7 +535,6 @@ def download_result( RuntimeError: if hybrid job is in a FAILED or CANCELLED state. TimeoutError: if hybrid job execution exceeds the polling timeout period. """ - extract_to = extract_to or Path.cwd() timeout_time = time.time() + poll_timeout_seconds @@ -576,7 +584,7 @@ def _extract_tar_file(extract_path: str) -> None: def __repr__(self) -> str: return f"AwsQuantumJob('arn':'{self.arn}')" - def __eq__(self, other) -> bool: + def __eq__(self, other: AwsQuantumJob) -> bool: if isinstance(other, AwsQuantumJob): return self.arn == other.arn return False diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 34f22ae17..c6ad36b27 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -17,7 +17,7 @@ import time from functools import singledispatch from logging import Logger, getLogger -from typing import Any, Optional, Union +from typing import Any, ClassVar, Optional, Union import boto3 @@ -75,12 +75,13 @@ class AwsQuantumTask(QuantumTask): """Amazon Braket implementation of a quantum task. A quantum task can be a circuit, - an OpenQASM program or an AHS program.""" + an OpenQASM program or an AHS program. + """ # TODO: Add API documentation that defines these states. Make it clear this is the contract. - NO_RESULT_TERMINAL_STATES = {"FAILED", "CANCELLED"} - RESULTS_READY_STATES = {"COMPLETED"} - TERMINAL_STATES = RESULTS_READY_STATES.union(NO_RESULT_TERMINAL_STATES) + NO_RESULT_TERMINAL_STATES: ClassVar[set[str]] = {"FAILED", "CANCELLED"} + RESULTS_READY_STATES: ClassVar[set[str]] = {"COMPLETED"} + TERMINAL_STATES: ClassVar[set[str]] = RESULTS_READY_STATES.union(NO_RESULT_TERMINAL_STATES) DEFAULT_RESULTS_POLL_TIMEOUT = 432000 DEFAULT_RESULTS_POLL_INTERVAL = 1 @@ -174,7 +175,7 @@ def create( See Also: `braket.aws.aws_quantum_simulator.AwsQuantumSimulator.run()` `braket.aws.aws_qpu.AwsQpu.run()` - """ + """ # noqa E501 if len(s3_destination_folder) != 2: raise ValueError( "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." @@ -207,7 +208,7 @@ def create( unbounded_parameters = param_names - set(inputs.keys()) if unbounded_parameters: raise ValueError( - f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" + f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" ) return _create_internal( @@ -233,7 +234,8 @@ def __init__( logger: Logger = getLogger(__name__), quiet: bool = False, ): - """ + """Initializes an `AwsQuantumTask`. + Args: arn (str): The ARN of the quantum task. aws_session (AwsSession | None): The `AwsSession` for connecting to AWS services. @@ -258,7 +260,6 @@ def __init__( >>> result = task.result() GateModelQuantumTaskResult(...) """ - self._arn: str = arn self._aws_session: AwsSession = aws_session or AwsQuantumTask._aws_session_for_task_arn( task_arn=arn @@ -276,9 +277,8 @@ def __init__( @staticmethod def _aws_session_for_task_arn(task_arn: str) -> AwsSession: - """ - Get an AwsSession for the Quantum Task ARN. The AWS session should be in the region of the - quantum task. + """Get an AwsSession for the Quantum Task ARN. The AWS session should be in the region of + the quantum task. Returns: AwsSession: `AwsSession` object with default `boto_session` in quantum task's region. @@ -302,19 +302,20 @@ def _cancel_future(self) -> None: def cancel(self) -> None: """Cancel the quantum task. This cancels the future and the quantum task in Amazon - Braket.""" + Braket. + """ self._cancel_future() self._aws_session.cancel_quantum_task(self._arn) def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: - """ - Get quantum task metadata defined in Amazon Braket. + """Get quantum task metadata defined in Amazon Braket. Args: use_cached_value (bool): If `True`, uses the value most recently retrieved from the Amazon Braket `GetQuantumTask` operation, if it exists; if not, `GetQuantumTask` will be called to retrieve the metadata. If `False`, always calls `GetQuantumTask`, which also updates the cached value. Default: `False`. + Returns: dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. If `use_cached_value` is `True`, Amazon Braket is not called and the most recently @@ -326,26 +327,26 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: return self._metadata def state(self, use_cached_value: bool = False) -> str: - """ - The state of the quantum task. + """The state of the quantum task. Args: use_cached_value (bool): If `True`, uses the value most recently retrieved from the Amazon Braket `GetQuantumTask` operation. If `False`, calls the `GetQuantumTask` operation to retrieve metadata, which also updates the cached value. Default = `False`. + Returns: str: The value of `status` in `metadata()`. This is the value of the `status` key in the Amazon Braket `GetQuantumTask` operation. If `use_cached_value` is `True`, the value most recently returned from the `GetQuantumTask` operation is used. + See Also: `metadata()` """ return self._status(use_cached_value) def queue_position(self) -> QuantumTaskQueueInfo: - """ - The queue position details for the quantum task. + """The queue position details for the quantum task. Returns: QuantumTaskQueueInfo: Instance of QuantumTaskQueueInfo class @@ -399,8 +400,7 @@ def result( ) -> Union[ GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult ]: - """ - Get the quantum task result by polling Amazon Braket to see if the task is completed. + """Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the quantum task is completed, the result is retrieved from S3 and returned as a `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` @@ -409,10 +409,10 @@ def result( Consecutive calls to this method return a cached result from the preceding request. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: # noqa - The result of the quantum task, if the quantum task completed successfully; returns + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: The + result of the quantum task, if the quantum task completed successfully; returns `None` if the quantum task did not complete successfully or the future timed out. - """ + """ # noqa E501 if self._result or ( self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES ): @@ -445,15 +445,13 @@ def _get_future(self) -> asyncio.Future: return self._future def async_result(self) -> asyncio.Task: - """ - Get the quantum task result asynchronously. Consecutive calls to this method return + """Get the quantum task result asynchronously. Consecutive calls to this method return the result cached from the most recent request. """ return self._get_future() async def _create_future(self) -> asyncio.Task: - """ - Wrap the `_wait_for_completion` coroutine inside a future-like object. + """Wrap the `_wait_for_completion` coroutine inside a future-like object. Invoking this method starts the coroutine and returns back the future-like object that contains it. Note that this does not block on the coroutine to finish. @@ -467,19 +465,19 @@ async def _wait_for_completion( ) -> Union[ GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult ]: - """ - Waits for the quantum task to be completed, then returns the result from the S3 bucket. + """Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: If the task is in the + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: If the task is in the `AwsQuantumTask.RESULTS_READY_STATES` state within the specified time limit, the result from the S3 bucket is loaded and returned. `None` is returned if a timeout occurs or task state is in `AwsQuantumTask.NO_RESULT_TERMINAL_STATES`. + Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. - """ + """ # noqa E501 self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() @@ -545,7 +543,7 @@ def _download_result( def __repr__(self) -> str: return f"AwsQuantumTask('id/taskArn':'{self.id}')" - def __eq__(self, other) -> bool: + def __eq__(self, other: AwsQuantumTask) -> bool: if isinstance(other, AwsQuantumTask): return self.id == other.id return False @@ -621,7 +619,7 @@ def _( device_arn, GateModelParameters(qubitCount=0), # qubitCount unused ) - if type(device_parameters) is dict + if isinstance(device_parameters, dict) else device_parameters ) create_task_kwargs.update( @@ -671,7 +669,7 @@ def _( ) final_device_parameters = ( _circuit_device_params_from_dict(device_parameters or {}, device_arn, paradigm_parameters) - if type(device_parameters) is dict + if isinstance(device_parameters, dict) else device_parameters ) @@ -725,7 +723,7 @@ def _( DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters, ], - _, + _: bool, inputs: dict[str, float], gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], *args, @@ -750,7 +748,7 @@ def _( create_task_kwargs: dict[str, Any], device_arn: str, device_parameters: dict, - _, + _: AnalogHamiltonianSimulationTaskResult, inputs: dict[str, float], gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], *args, @@ -793,7 +791,7 @@ def _create_annealing_device_params( Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: The device parameters. """ - if type(device_params) is not dict: + if not isinstance(device_params, dict): device_params = device_params.dict() # check for device level or provider level parameters @@ -834,7 +832,7 @@ def _create_common_params( @singledispatch def _format_result( - result: Union[GateModelTaskResult, AnnealingTaskResult, PhotonicModelTaskResult] + result: Union[GateModelTaskResult, AnnealingTaskResult, PhotonicModelTaskResult], ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: raise TypeError("Invalid result specification type") diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 6c505e6ef..a02dfa6d6 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -16,7 +16,7 @@ import time from concurrent.futures.thread import ThreadPoolExecutor from itertools import repeat -from typing import Union +from typing import Any, Union from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem @@ -62,8 +62,8 @@ def __init__( poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] | None = None, reservation_arn: str | None = None, - *aws_quantum_task_args, - **aws_quantum_task_kwargs, + *aws_quantum_task_args: Any, + **aws_quantum_task_kwargs: Any, ): """Creates a batch of quantum tasks. @@ -97,7 +97,9 @@ def __init__( Note: If you are creating tasks in a job that itself was created reservation ARN, those tasks do not need to be created with the reservation ARN. Default: None. - """ + *aws_quantum_task_args (Any): Arbitrary args for `QuantumTask`. + **aws_quantum_task_kwargs (Any): Arbitrary kwargs for `QuantumTask`., + """ # noqa E501 self._tasks = AwsQuantumTaskBatch._execute( aws_session, device_arn, @@ -166,10 +168,7 @@ def _tasks_and_inputs( if not single_task and not single_input: if len(task_specifications) != len(inputs): - raise ValueError( - "Multiple inputs and task specifications must " "be equal in number." - ) - + raise ValueError("Multiple inputs and task specifications must be equal in number.") if single_task: task_specifications = repeat(task_specifications, times=max_inputs_tasks) @@ -386,7 +385,8 @@ def retry_unsuccessful_tasks(self) -> bool: @property def tasks(self) -> list[AwsQuantumTask]: """list[AwsQuantumTask]: The quantum tasks in this batch, as a list of AwsQuantumTask - objects""" + objects + """ return list(self._tasks) @property @@ -397,6 +397,7 @@ def size(self) -> int: @property def unfinished(self) -> set[str]: """Gets all the IDs of all the quantum tasks in teh batch that have yet to complete. + Returns: set[str]: The IDs of all the quantum tasks in the batch that have yet to complete. """ @@ -414,5 +415,6 @@ def unfinished(self) -> set[str]: @property def unsuccessful(self) -> set[str]: """set[str]: The IDs of all the FAILED, CANCELLED, or timed out quantum tasks in the - batch.""" + batch. + """ return set(self._unsuccessful) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 0534871a2..6c63a7e45 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -33,10 +33,14 @@ from braket.tracking.tracking_events import _TaskCreationEvent, _TaskStatusEvent -class AwsSession(object): +class AwsSession: """Manage interactions with AWS services.""" - S3DestinationFolder = NamedTuple("S3DestinationFolder", [("bucket", str), ("key", str)]) + class S3DestinationFolder(NamedTuple): + """A `NamedTuple` for an S3 bucket and object key.""" + + bucket: str + key: str def __init__( self, @@ -45,12 +49,16 @@ def __init__( config: Config | None = None, default_bucket: str | None = None, ): - """ + """Initializes an `AwsSession`. + Args: - boto_session (Session | None): A boto3 session object. + boto_session (boto3.Session | None): A boto3 session object. braket_client (client | None): A boto3 Braket client. config (Config | None): A botocore Config object. default_bucket (str | None): The name of the default bucket of the AWS Session. + + Raises: + ValueError: invalid boto_session or braket_client. """ if ( boto_session @@ -158,8 +166,7 @@ def ecr_client(self) -> client: return self._ecr def _update_user_agent(self) -> None: - """ - Updates the `User-Agent` header forwarded by boto3 to include the braket-sdk, + """Updates the `User-Agent` header forwarded by boto3 to include the braket-sdk, braket-schemas and the notebook instance version. The header is a string of space delimited values (For example: "Boto3/1.14.43 Python/3.7.9 Botocore/1.17.44"). """ @@ -176,8 +183,7 @@ def _notebook_instance_version() -> str: ) def add_braket_user_agent(self, user_agent: str) -> None: - """ - Appends the `user-agent` value to the User-Agent header, if it does not yet exist in the + """Appends the `user-agent` value to the User-Agent header, if it does not yet exist in the header. This method is typically only relevant for libraries integrating with the Amazon Braket SDK. @@ -204,8 +210,7 @@ def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> # Quantum Tasks # def cancel_quantum_task(self, arn: str) -> None: - """ - Cancel the quantum task. + """Cancel the quantum task. Args: arn (str): The ARN of the quantum task to cancel. @@ -214,11 +219,10 @@ def cancel_quantum_task(self, arn: str) -> None: broadcast_event(_TaskStatusEvent(arn=arn, status=response["cancellationStatus"])) def create_quantum_task(self, **boto3_kwargs) -> str: - """ - Create a quantum task. + """Create a quantum task. Args: - ``**boto3_kwargs``: Keyword arguments for the Amazon Braket `CreateQuantumTask` + **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateQuantumTask` operation. Returns: @@ -240,11 +244,10 @@ def create_quantum_task(self, **boto3_kwargs) -> str: return response["quantumTaskArn"] def create_job(self, **boto3_kwargs) -> str: - """ - Create a quantum hybrid job. + """Create a quantum hybrid job. Args: - ``**boto3_kwargs``: Keyword arguments for the Amazon Braket `CreateJob` operation. + **boto3_kwargs: Keyword arguments for the Amazon Braket `CreateJob` operation. Returns: str: The ARN of the hybrid job. @@ -271,8 +274,7 @@ def _should_giveup(err: Exception) -> bool: giveup=_should_giveup.__func__, ) def get_quantum_task(self, arn: str) -> dict[str, Any]: - """ - Gets the quantum task. + """Gets the quantum task. Args: arn (str): The ARN of the quantum task to get. @@ -287,9 +289,8 @@ def get_quantum_task(self, arn: str) -> dict[str, Any]: return response def get_default_jobs_role(self) -> str: - """ - Returns the role ARN for the default hybrid jobs role created in the Amazon Braket Console. - It will pick the first role it finds with the `RoleName` prefix + """This returns the role ARN for the default hybrid jobs role created in the Amazon Braket + Console. It will pick the first role it finds with the `RoleName` prefix `AmazonBraketJobsExecutionRole` with a `PathPrefix` of `/service-role/`. Returns: @@ -298,7 +299,7 @@ def get_default_jobs_role(self) -> str: Raises: RuntimeError: If no roles can be found with the prefix - `/service-role/AmazonBraketJobsExecutionRole`. + `/service-role/AmazonBraketJobsExecutionRole`. """ roles_paginator = self.iam_client.get_paginator("list_roles") for page in roles_paginator.paginate(PathPrefix="/service-role/"): @@ -318,8 +319,7 @@ def get_default_jobs_role(self) -> str: giveup=_should_giveup.__func__, ) def get_job(self, arn: str) -> dict[str, Any]: - """ - Gets the hybrid job. + """Gets the hybrid job. Args: arn (str): The ARN of the hybrid job to get. @@ -330,8 +330,7 @@ def get_job(self, arn: str) -> dict[str, Any]: return self.braket_client.get_job(jobArn=arn, additionalAttributeNames=["QueueInfo"]) def cancel_job(self, arn: str) -> dict[str, Any]: - """ - Cancel the hybrid job. + """Cancel the hybrid job. Args: arn (str): The ARN of the hybrid job to cancel. @@ -342,8 +341,7 @@ def cancel_job(self, arn: str) -> dict[str, Any]: return self.braket_client.cancel_job(jobArn=arn) def retrieve_s3_object_body(self, s3_bucket: str, s3_object_key: str) -> str: - """ - Retrieve the S3 object body. + """Retrieve the S3 object body. Args: s3_bucket (str): The S3 bucket name. @@ -367,8 +365,7 @@ def upload_to_s3(self, filename: str, s3_uri: str) -> None: self.s3_client.upload_file(filename, bucket, key) def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: - """ - Upload local data matching a prefix to a corresponding location in S3 + """Upload local data matching a prefix to a corresponding location in S3 Args: local_prefix (str): a prefix designating files to be uploaded to S3. All files @@ -410,8 +407,7 @@ def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: self.upload_to_s3(str(file), s3_uri) def download_from_s3(self, s3_uri: str, filename: str) -> None: - """ - Download file from S3 + """Download file from S3 Args: s3_uri (str): The S3 uri from where the file will be downloaded. @@ -421,8 +417,7 @@ def download_from_s3(self, s3_uri: str, filename: str) -> None: self.s3_client.download_file(bucket, key, filename) def copy_s3_object(self, source_s3_uri: str, destination_s3_uri: str) -> None: - """ - Copy object from another location in s3. Does nothing if source and + """Copy object from another location in s3. Does nothing if source and destination URIs are the same. Args: @@ -445,8 +440,7 @@ def copy_s3_object(self, source_s3_uri: str, destination_s3_uri: str) -> None: ) def copy_s3_directory(self, source_s3_path: str, destination_s3_path: str) -> None: - """ - Copy all objects from a specified directory in S3. Does nothing if source and + """Copy all objects from a specified directory in S3. Does nothing if source and destination URIs are the same. Preserves nesting structure, will not overwrite other files in the destination location unless they share a name with a file being copied. @@ -475,8 +469,7 @@ def copy_s3_directory(self, source_s3_path: str, destination_s3_path: str) -> No ) def list_keys(self, bucket: str, prefix: str) -> list[str]: - """ - Lists keys matching prefix in bucket. + """Lists keys matching prefix in bucket. Args: bucket (str): Bucket to be queried. @@ -501,8 +494,7 @@ def list_keys(self, bucket: str, prefix: str) -> list[str]: return keys def default_bucket(self) -> str: - """ - Returns the name of the default bucket of the AWS Session. In the following order + """Returns the name of the default bucket of the AWS Session. In the following order of priority, it will return either the parameter `default_bucket` set during initialization of the AwsSession (if not None), the bucket being used by the currently running Braket Hybrid Job (if evoked inside of a Braket Hybrid Job), or a default @@ -598,8 +590,7 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) raise def get_device(self, arn: str) -> dict[str, Any]: - """ - Calls the Amazon Braket `get_device` API to retrieve device metadata. + """Calls the Amazon Braket `get_device` API to retrieve device metadata. Args: arn (str): The ARN of the device. @@ -617,8 +608,7 @@ def search_devices( statuses: Optional[list[str]] = None, provider_names: Optional[list[str]] = None, ) -> list[dict[str, Any]]: - """ - Get devices based on filters. The result is the AND of + """Get devices based on filters. The result is the AND of all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Args: @@ -657,6 +647,7 @@ def search_devices( @staticmethod def is_s3_uri(string: str) -> bool: """Determines if a given string is an S3 URI. + Args: string (str): the string to check. @@ -671,8 +662,7 @@ def is_s3_uri(string: str) -> bool: @staticmethod def parse_s3_uri(s3_uri: str) -> tuple[str, str]: - """ - Parse S3 URI to get bucket and key + """Parse S3 URI to get bucket and key Args: s3_uri (str): S3 URI. @@ -690,9 +680,9 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]: s3_uri_match = re.match(r"^https://([^./]+)\.[sS]3\.[^/]+/(.+)$", s3_uri) or re.match( r"^[sS]3://([^./]+)/(.+)$", s3_uri ) - assert s3_uri_match + if s3_uri_match is None: + raise AssertionError bucket, key = s3_uri_match.groups() - assert bucket and key return bucket, key except (AssertionError, ValueError): raise ValueError(f"Not a valid S3 uri: {s3_uri}") @@ -703,7 +693,7 @@ def construct_s3_uri(bucket: str, *dirs: str) -> str: Args: bucket (str): S3 URI. - ``*dirs`` (str): directories to be appended in the resulting S3 URI + *dirs (str): directories to be appended in the resulting S3 URI Returns: str: S3 URI @@ -723,8 +713,7 @@ def describe_log_streams( limit: Optional[int] = None, next_token: Optional[str] = None, ) -> dict[str, Any]: - """ - Describes CloudWatch log streams in a log group with a given prefix. + """Describes CloudWatch log streams in a log group with a given prefix. Args: log_group (str): Name of the log group. @@ -759,8 +748,7 @@ def get_log_events( start_from_head: bool = True, next_token: Optional[str] = None, ) -> dict[str, Any]: - """ - Gets CloudWatch log events from a given log stream. + """Gets CloudWatch log events from a given log stream. Args: log_group (str): Name of the log group. @@ -791,8 +779,7 @@ def copy_session( region: Optional[str] = None, max_connections: Optional[int] = None, ) -> AwsSession: - """ - Creates a new AwsSession based on the region. + """Creates a new AwsSession based on the region. Args: region (Optional[str]): Name of the region. Default = `None`. @@ -833,8 +820,7 @@ def copy_session( @cache def get_full_image_tag(self, image_uri: str) -> str: - """ - Get verbose image tag from image uri. + """Get verbose image tag from image uri. Args: image_uri (str): Image uri to get tag for. diff --git a/src/braket/aws/queue_information.py b/src/braket/aws/queue_information.py index 109632751..77e5f3554 100644 --- a/src/braket/aws/queue_information.py +++ b/src/braket/aws/queue_information.py @@ -17,8 +17,7 @@ class QueueType(str, Enum): - """ - Enumerates the possible priorities for the queue. + """Enumerates the possible priorities for the queue. Values: NORMAL: Represents normal queue for the device. @@ -31,8 +30,7 @@ class QueueType(str, Enum): @dataclass() class QueueDepthInfo: - """ - Represents quantum tasks and hybrid jobs queue depth information. + """Represents quantum tasks and hybrid jobs queue depth information. Attributes: quantum_tasks (dict[QueueType, str]): number of quantum tasks waiting @@ -49,8 +47,7 @@ class QueueDepthInfo: @dataclass class QuantumTaskQueueInfo: - """ - Represents quantum tasks queue information. + """Represents quantum tasks queue information. Attributes: queue_type (QueueType): type of the quantum_task queue either 'Normal' @@ -68,8 +65,7 @@ class QuantumTaskQueueInfo: @dataclass class HybridJobQueueInfo: - """ - Represents hybrid job queue information. + """Represents hybrid job queue information. Attributes: queue_position (Optional[str]): current position of your hybrid job within a respective diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index e453177a7..5b4c4faba 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -27,9 +27,7 @@ class AngledGate(Gate, Parameterizable): - """ - Class `AngledGate` represents a quantum gate that operates on N qubits and an angle. - """ + """Class `AngledGate` represents a quantum gate that operates on N qubits and an angle.""" def __init__( self, @@ -37,7 +35,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Initializes an `AngledGate`. + Args: angle (Union[FreeParameterExpression, float]): The angle of the gate in radians or expression representation. @@ -63,8 +62,7 @@ def __init__( @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameters or + """Returns the parameters associated with the object, either unbound free parameters or bound values. Returns: @@ -75,8 +73,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: @property def angle(self) -> Union[FreeParameterExpression, float]: - """ - Returns the angle for the gate + """Returns the angle of the gate Returns: Union[FreeParameterExpression, float]: The angle of the gate in radians @@ -110,7 +107,7 @@ def adjoint(self) -> list[Gate]: new._ascii_symbols = new_ascii_symbols return [new] - def __eq__(self, other): + def __eq__(self, other: AngledGate): return ( isinstance(other, AngledGate) and self.name == other.name @@ -125,8 +122,8 @@ def __hash__(self): class DoubleAngledGate(Gate, Parameterizable): - """ - Class `DoubleAngledGate` represents a quantum gate that operates on N qubits and two angles. + """Class `DoubleAngledGate` represents a quantum gate that operates on N qubits and + two angles. """ def __init__( @@ -136,7 +133,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Inits a `DoubleAngledGate`. + Args: angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in radians or expression representation. @@ -168,8 +166,7 @@ def __init__( @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameters or + """Returns the parameters associated with the object, either unbound free parameters or bound values. Returns: @@ -180,8 +177,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: @property def angle_1(self) -> Union[FreeParameterExpression, float]: - """ - Returns the first angle for the gate + """Returns the first angle of the gate Returns: Union[FreeParameterExpression, float]: The first angle of the gate in radians @@ -190,20 +186,18 @@ def angle_1(self) -> Union[FreeParameterExpression, float]: @property def angle_2(self) -> Union[FreeParameterExpression, float]: - """ - Returns the second angle for the gate + """Returns the second angle of the gate Returns: Union[FreeParameterExpression, float]: The second angle of the gate in radians """ return self._parameters[1] - def bind_values(self, **kwargs) -> AngledGate: - """ - Takes in parameters and attempts to assign them to values. + def bind_values(self, **kwargs: FreeParameterExpression | str) -> AngledGate: + """Takes in parameters and attempts to assign them to values. Args: - ``**kwargs``: The parameters that are being assigned. + **kwargs (FreeParameterExpression | str): The parameters that are being assigned. Returns: AngledGate: A new Gate of the same type with the requested parameters bound. @@ -221,7 +215,7 @@ def adjoint(self) -> list[Gate]: """ raise NotImplementedError - def __eq__(self, other): + def __eq__(self, other: DoubleAngledGate): return ( isinstance(other, DoubleAngledGate) and self.name == other.name @@ -240,8 +234,8 @@ def __hash__(self): class TripleAngledGate(Gate, Parameterizable): - """ - Class `TripleAngledGate` represents a quantum gate that operates on N qubits and three angles. + """Class `TripleAngledGate` represents a quantum gate that operates on N qubits and + three angles. """ def __init__( @@ -252,7 +246,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Inits a `TripleAngledGate`. + Args: angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in radians or expression representation. @@ -287,8 +282,7 @@ def __init__( @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameters or + """Returns the parameters associated with the object, either unbound free parameters or bound values. Returns: @@ -299,8 +293,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: @property def angle_1(self) -> Union[FreeParameterExpression, float]: - """ - Returns the first angle for the gate + """Returns the first angle of the gate Returns: Union[FreeParameterExpression, float]: The first angle of the gate in radians @@ -309,8 +302,7 @@ def angle_1(self) -> Union[FreeParameterExpression, float]: @property def angle_2(self) -> Union[FreeParameterExpression, float]: - """ - Returns the second angle for the gate + """Returns the second angle of the gate Returns: Union[FreeParameterExpression, float]: The second angle of the gate in radians @@ -319,20 +311,18 @@ def angle_2(self) -> Union[FreeParameterExpression, float]: @property def angle_3(self) -> Union[FreeParameterExpression, float]: - """ - Returns the second angle for the gate + """Returns the third angle of the gate Returns: Union[FreeParameterExpression, float]: The third angle of the gate in radians """ return self._parameters[2] - def bind_values(self, **kwargs) -> AngledGate: - """ - Takes in parameters and attempts to assign them to values. + def bind_values(self, **kwargs: FreeParameterExpression | str) -> AngledGate: + """Takes in parameters and attempts to assign them to values. Args: - ``**kwargs``: The parameters that are being assigned. + **kwargs (FreeParameterExpression | str): The parameters that are being assigned. Returns: AngledGate: A new Gate of the same type with the requested parameters bound. @@ -350,7 +340,7 @@ def adjoint(self) -> list[Gate]: """ raise NotImplementedError - def __eq__(self, other): + def __eq__(self, other: TripleAngledGate): return ( isinstance(other, TripleAngledGate) and self.name == other.name @@ -382,8 +372,7 @@ def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: - """ - Generates a formatted ascii representation of an angled gate. + """Generates a formatted ascii representation of an angled gate. Args: gate (str): The name of the gate. @@ -400,24 +389,22 @@ def _multi_angled_ascii_characters( gate: str, *angles: Union[FreeParameterExpression, float], ) -> str: - """ - Generates a formatted ascii representation of an angled gate. + """Generates a formatted ascii representation of an angled gate. Args: gate (str): The name of the gate. - `*angles` (Union[FreeParameterExpression, float]): angles in radians. + *angles (Union[FreeParameterExpression, float]): angles in radians. Returns: str: Returns the ascii representation for an angled gate. """ - def format_string(angle: Union[FreeParameterExpression, float]) -> str: - """ - Formats an angle for ASCII representation. + def format_string(angle: FreeParameterExpression | float) -> str: + """Formats an angle for ASCII representation. Args: - angle (Union[FreeParameterExpression, float]): The angle to format. + angle (FreeParameterExpression | float): The angle to format. Returns: str: The ASCII representation of the angle. @@ -427,13 +414,13 @@ def format_string(angle: Union[FreeParameterExpression, float]) -> str: return f"{gate}({', '.join(f'{angle:{format_string(angle)}}' for angle in angles)})" -def get_angle(gate: AngledGate, **kwargs) -> AngledGate: - """ - Gets the angle with all values substituted in that are requested. +def get_angle(gate: AngledGate, **kwargs: FreeParameterExpression | str) -> AngledGate: + """Gets the angle with all values substituted in that are requested. Args: gate (AngledGate): The subclass of AngledGate for which the angle is being obtained. - ``**kwargs``: The named parameters that are being filled for a particular gate. + **kwargs (FreeParameterExpression | str): The named parameters that are being filled + for a particular gate. Returns: AngledGate: A new gate of the type of the AngledGate originally used with all @@ -445,14 +432,16 @@ def get_angle(gate: AngledGate, **kwargs) -> AngledGate: return type(gate)(angle=new_angle) -def _get_angles(gate: TripleAngledGate, **kwargs) -> TripleAngledGate: - """ - Gets the angle with all values substituted in that are requested. +def _get_angles( + gate: TripleAngledGate, **kwargs: FreeParameterExpression | str +) -> TripleAngledGate: + """Gets the angle with all values substituted in that are requested. Args: gate (TripleAngledGate): The subclass of TripleAngledGate for which the angle is being obtained. - ``**kwargs``: The named parameters that are being filled for a particular gate. + **kwargs (FreeParameterExpression | str): The named parameters that are being filled + for a particular gate. Returns: TripleAngledGate: A new gate of the type of the AngledGate originally used with all angles diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index 2c7024574..c255377b5 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -33,16 +33,14 @@ class AsciiCircuitDiagram(CircuitDiagram): @staticmethod def build_diagram(circuit: cir.Circuit) -> str: - """ - Build an ASCII string circuit diagram. + """Build an ASCII string circuit diagram. Args: - circuit (Circuit): Circuit for which to build a diagram. + circuit (cir.Circuit): Circuit for which to build a diagram. Returns: str: ASCII string circuit diagram. """ - if not circuit.instructions: return "" @@ -128,8 +126,7 @@ def _prepare_diagram_vars( def _compute_moment_global_phase( global_phase: float | None, items: list[Instruction] ) -> float | None: - """ - Compute the integrated phase at a certain moment. + """Compute the integrated phase at a certain moment. Args: global_phase (float | None): The integrated phase up to the computed moment @@ -153,8 +150,7 @@ def _ascii_group_items( circuit_qubits: QubitSet, items: list[Union[Instruction, ResultType]], ) -> list[tuple[QubitSet, list[Instruction]]]: - """ - Group instructions in a moment for ASCII diagram + """Group instructions in a moment for ASCII diagram Args: circuit_qubits (QubitSet): set of qubits in circuit @@ -212,8 +208,7 @@ def _ascii_group_items( def _categorize_result_types( result_types: list[ResultType], ) -> tuple[list[str], list[ResultType]]: - """ - Categorize result types into result types with target and those without. + """Categorize result types into result types with target and those without. Args: result_types (list[ResultType]): list of result types @@ -239,8 +234,7 @@ def _ascii_diagram_column_set( items: list[Union[Instruction, ResultType]], global_phase: float | None, ) -> str: - """ - Return a set of columns in the ASCII string diagram of the circuit for a list of items. + """Return a set of columns in the ASCII string diagram of the circuit for a list of items. Args: col_title (str): title of column set @@ -251,7 +245,6 @@ def _ascii_diagram_column_set( Returns: str: An ASCII string diagram for the column set. """ - # Group items to separate out overlapping multi-qubit items groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) @@ -287,8 +280,7 @@ def _ascii_diagram_column( items: list[Union[Instruction, ResultType]], global_phase: float | None = None, ) -> str: - """ - Return a column in the ASCII string diagram of the circuit for a given list of items. + """Return a column in the ASCII string diagram of the circuit for a given list of items. Args: circuit_qubits (QubitSet): qubits in circuit @@ -317,7 +309,7 @@ def _ascii_diagram_column( marker = "*" * len(ascii_symbol) num_after = len(circuit_qubits) - 1 after = ["|"] * (num_after - 1) + ([marker] if num_after else []) - ascii_symbols = [ascii_symbol] + after + ascii_symbols = [ascii_symbol, *after] elif ( isinstance(item, Instruction) and isinstance(item.operator, Gate) @@ -412,9 +404,7 @@ def _create_output( def _build_map_control_qubits(item: Instruction, control_qubits: QubitSet) -> dict(Qubit, int): control_state = getattr(item, "control_state", None) if control_state is not None: - map_control_qubit_states = { - qubit: state for qubit, state in zip(control_qubits, control_state) - } + map_control_qubit_states = dict(zip(control_qubits, control_state)) else: map_control_qubit_states = {qubit: 1 for qubit in control_qubits} diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index b6ce11bc8..86578fc89 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import singledispatch from typing import Optional, Union @@ -5,7 +7,7 @@ class BasisState: - def __init__(self, state: "BasisStateInput", size: Optional[int] = None): + def __init__(self, state: BasisStateInput, size: Optional[int] = None): self.state = _as_tuple(state, size) @property @@ -30,7 +32,7 @@ def __len__(self) -> int: def __iter__(self): return iter(self.state) - def __eq__(self, other): + def __eq__(self, other: BasisState): return self.state == other.state def __bool__(self): @@ -42,7 +44,7 @@ def __str__(self): def __repr__(self): return f'BasisState("{self.as_string}")' - def __getitem__(self, item): + def __getitem__(self, item: int): return BasisState(self.state[item]) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 863513565..46d6e3b0b 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -31,7 +31,8 @@ class BraketProgramContext(AbstractProgramContext): def __init__(self, circuit: Optional[Circuit] = None): - """ + """Inits a `BraketProgramContext`. + Args: circuit (Optional[Circuit]): A partially-built circuit to continue building with this context. Default: None. @@ -133,8 +134,7 @@ def add_kraus_instruction(self, matrices: list[np.ndarray], target: list[int]) - self._circuit.add_instruction(instruction) def add_result(self, result: Results) -> None: - """ - Abstract method to add result type to the circuit + """Abstract method to add result type to the circuit Args: result (Results): The result object representing the measurement results diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index adebae07d..b3063c444 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -70,8 +70,7 @@ class Circuit: - """ - A representation of a quantum circuit that contains the instructions to be performed on a + """A representation of a quantum circuit that contains the instructions to be performed on a quantum device and the requested result types. See :mod:`braket.circuits.gates` module for all of the supported instructions. @@ -86,8 +85,7 @@ class Circuit: @classmethod def register_subroutine(cls, func: SubroutineCallable) -> None: - """ - Register the subroutine `func` as an attribute of the `Circuit` class. The attribute name + """Register the subroutine `func` as an attribute of the `Circuit` class. The attribute name is the name of `func`. Args: @@ -116,10 +114,11 @@ def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: setattr(cls, function_name, method_from_subroutine) function_attr = getattr(cls, function_name) - setattr(function_attr, "__doc__", func.__doc__) + function_attr.__doc__ = func.__doc__ def __init__(self, addable: AddableTypes | None = None, *args, **kwargs): - """ + """Inits a `Circuit`. + Args: addable (AddableTypes | None): The item(s) to add to self. Default = None. @@ -223,6 +222,7 @@ def moments(self) -> Moments: @property def qubit_count(self) -> int: """Get the qubit count for this circuit. Note that this includes observables. + Returns: int: The qubit count for this circuit. """ @@ -236,8 +236,7 @@ def qubits(self) -> QubitSet: @property def parameters(self) -> set[FreeParameter]: - """ - Gets a set of the parameters in the Circuit. + """Gets a set of the parameters in the Circuit. Returns: set[FreeParameter]: The `FreeParameters` in the Circuit. @@ -250,8 +249,7 @@ def add_result_type( target: QubitSetInput | None = None, target_mapping: dict[QubitInput, QubitInput] | None = None, ) -> Circuit: - """ - Add a requested result type to `self`, returns `self` for chaining ability. + """Add a requested result type to `self`, returns `self` for chaining ability. Args: result_type (ResultType): `ResultType` to add into `self`. @@ -414,8 +412,7 @@ def add_instruction( target: QubitSetInput | None = None, target_mapping: dict[QubitInput, QubitInput] | None = None, ) -> Circuit: - """ - Add an instruction to `self`, returns `self` for chaining ability. + """Add an instruction to `self`, returns `self` for chaining ability. Args: instruction (Instruction): `Instruction` to add into `self`. @@ -484,8 +481,7 @@ def add_instruction( return self def _check_for_params(self, instruction: Instruction) -> bool: - """ - This checks for free parameters in an :class:{Instruction}. Checks children classes of + """This checks for free parameters in an :class:{Instruction}. Checks children classes of :class:{Parameterizable}. Args: @@ -506,8 +502,7 @@ def add_circuit( target: QubitSetInput | None = None, target_mapping: dict[QubitInput, QubitInput] | None = None, ) -> Circuit: - """ - Add a `circuit` to self, returns self for chaining ability. + """Add a `Circuit` to `self`, returning `self` for chaining ability. Args: circuit (Circuit): Circuit to add into self. @@ -582,9 +577,8 @@ def add_verbatim_box( target: QubitSetInput | None = None, target_mapping: dict[QubitInput, QubitInput] | None = None, ) -> Circuit: - """ - Add a verbatim `circuit` to self, that is, ensures that `circuit` is not modified in any way - by the compiler. + """Add a verbatim `Circuit` to `self`, ensuring that the circuit is not modified in + any way by the compiler. Args: verbatim_circuit (Circuit): Circuit to add into self. @@ -700,7 +694,8 @@ def apply_gate_noise( If `target_unitary` is not a unitary. If `noise` is multi-qubit noise and `target_gates` contain gates with the number of qubits not the same as `noise.qubit_count`. - Warning: + + Warning: If `noise` is multi-qubit noise while there is no gate with the same number of qubits in `target_qubits` or in the whole circuit when `target_qubits` is not given. @@ -860,8 +855,7 @@ def apply_initialization_noise( return apply_noise_to_moments(self, noise, target_qubits, "initialization") def make_bound_circuit(self, param_values: dict[str, Number], strict: bool = False) -> Circuit: - """ - Binds FreeParameters based upon their name and values passed in. If parameters + """Binds `FreeParameter`s based upon their name and values passed in. If parameters share the same name, all the parameters of that name will be set to the mapped value. Args: @@ -879,16 +873,15 @@ def make_bound_circuit(self, param_values: dict[str, Number], strict: bool = Fal return self._use_parameter_value(param_values) def _validate_parameters(self, parameter_values: dict[str, Number]) -> None: - """ - This runs a check to see that the parameters are in the Circuit. + """Checks that the parameters are in the `Circuit`. Args: parameter_values (dict[str, Number]): A mapping of FreeParameter names to a value to assign to them. Raises: - ValueError: If a parameter name is given which does not appear in the circuit. - + ValueError: If there are no parameters that match the key for the arg + param_values. """ parameter_strings = set() for parameter in self.parameters: @@ -898,8 +891,7 @@ def _validate_parameters(self, parameter_values: dict[str, Number]) -> None: raise ValueError(f"No parameter in the circuit named: {param}") def _use_parameter_value(self, param_values: dict[str, Number]) -> Circuit: - """ - Creates a Circuit that uses the parameter values passed in. + """Creates a `Circuit` that uses the parameter values passed in. Args: param_values (dict[str, Number]): A mapping of FreeParameter names @@ -927,8 +919,7 @@ def _use_parameter_value(self, param_values: dict[str, Number]) -> Circuit: @staticmethod def _validate_parameter_value(val: Any) -> None: - """ - Validates the value being used is a Number. + """Validates the value being used is a `Number`. Args: val (Any): The value be verified. @@ -938,7 +929,7 @@ def _validate_parameter_value(val: Any) -> None: """ if not isinstance(val, Number): raise ValueError( - f"Parameters can only be assigned numeric values. " f"Invalid inputs: {val}" + f"Parameters can only be assigned numeric values. Invalid inputs: {val}" ) def apply_readout_noise( @@ -1020,8 +1011,7 @@ def apply_readout_noise( return apply_noise_to_moments(self, noise, target_qubits, "readout") def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: - """ - Generic add method for adding item(s) to self. Any arguments that + """Generic add method for adding item(s) to self. Any arguments that `add_circuit()` and / or `add_instruction()` and / or `add_result_type` supports are supported by this method. If adding a subroutine, check with that subroutines documentation to determine what @@ -1094,8 +1084,7 @@ def adjoint(self) -> Circuit: return circ def diagram(self, circuit_diagram_class: type = AsciiCircuitDiagram) -> str: - """ - Get a diagram for the current circuit. + """Get a diagram for the current circuit. Args: circuit_diagram_class (type): A `CircuitDiagram` class that builds the @@ -1109,21 +1098,20 @@ def diagram(self, circuit_diagram_class: type = AsciiCircuitDiagram) -> str: def to_ir( self, ir_type: IRType = IRType.JAQCD, - serialization_properties: Optional[SerializationProperties] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + serialization_properties: SerializationProperties | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, ) -> Union[OpenQasmProgram, JaqcdProgram]: - """ - Converts the circuit into the canonical intermediate representation. + """Converts the circuit into the canonical intermediate representation. If the circuit is sent over the wire, this method is called before it is sent. Args: ir_type (IRType): The IRType to use for converting the circuit object to its IR representation. - serialization_properties (Optional[SerializationProperties]): The serialization + serialization_properties (SerializationProperties | None): The serialization properties to use while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. - gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): The + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): The calibration data for the device. default: None. Returns: @@ -1132,7 +1120,7 @@ def to_ir( Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. + properties don't correspond to the `ir_type`. """ if ir_type == IRType.JAQCD: return self._to_jaqcd() @@ -1155,8 +1143,7 @@ def to_ir( def from_ir( source: Union[str, OpenQasmProgram], inputs: Optional[dict[str, io_type]] = None ) -> Circuit: - """ - Converts an OpenQASM program to a Braket Circuit object. + """Converts an OpenQASM program to a Braket Circuit object. Args: source (Union[str, OpenQasmProgram]): OpenQASM string. @@ -1260,7 +1247,7 @@ def _validate_gate_calbrations_uniqueness( frames: dict[str, Frame], waveforms: dict[str, Waveform], ) -> None: - for key, calibration in gate_definitions.items(): + for _key, calibration in gate_definitions.items(): for frame in calibration._frames.values(): _validate_uniqueness(frames, frame) frames[frame.id] = frame @@ -1420,8 +1407,7 @@ def _add_fixed_argument_calibrations( return additional_calibrations def to_unitary(self) -> np.ndarray: - """ - Returns the unitary matrix representation of the entire circuit. + """Returns the unitary matrix representation of the entire circuit. Note: The performance of this method degrades with qubit count. It might be slow for @@ -1484,8 +1470,7 @@ def _copy(self) -> Circuit: return copy def copy(self) -> Circuit: - """ - Return a shallow copy of the circuit. + """Return a shallow copy of the circuit. Returns: Circuit: A shallow copy of the circuit. @@ -1512,25 +1497,25 @@ def __repr__(self) -> str: def __str__(self): return self.diagram(AsciiCircuitDiagram) - def __eq__(self, other): + def __eq__(self, other: Circuit): if isinstance(other, Circuit): return ( self.instructions == other.instructions and self.result_types == other.result_types ) return NotImplemented - def __call__(self, arg: Any | None = None, **kwargs) -> Circuit: - """ - Implements the call function to easily make a bound Circuit. + def __call__(self, arg: Any | None = None, **kwargs: Any) -> Circuit: + """Implements the call function to easily make a bound Circuit. Args: arg (Any | None): A value to bind to all parameters. Defaults to None and can be overridden if the parameter is in kwargs. + **kwargs (Any): The parameter and valued to be bound. Returns: Circuit: A circuit with the specified parameters bound. """ - param_values = dict() + param_values = {} if arg is not None: for param in self.parameters: param_values[str(param)] = arg @@ -1540,8 +1525,7 @@ def __call__(self, arg: Any | None = None, **kwargs) -> Circuit: def subroutine(register: bool = False) -> Callable: - """ - Subroutine is a function that returns instructions, result types, or circuits. + """Subroutine is a function that returns instructions, result types, or circuits. Args: register (bool): If `True`, adds this subroutine into the `Circuit` class. diff --git a/src/braket/circuits/circuit_diagram.py b/src/braket/circuits/circuit_diagram.py index 5b156d290..cc39aa7ee 100644 --- a/src/braket/circuits/circuit_diagram.py +++ b/src/braket/circuits/circuit_diagram.py @@ -24,11 +24,10 @@ class CircuitDiagram(ABC): @staticmethod @abstractmethod def build_diagram(circuit: cir.Circuit) -> str: - """ - Build a diagram for the specified `circuit`. + """Build a diagram for the specified `circuit`. Args: - circuit (Circuit): The circuit to build a diagram for. + circuit (cir.Circuit): The circuit to build a diagram for. Returns: str: String representation for the circuit diagram. diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index f0e3f3144..1a50c4c83 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -15,8 +15,7 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: - """ - Validates if circuit and shots are correct before running on a device + """Validates if circuit and shots are correct before running on a device Args: circuit (Circuit): circuit to validate @@ -40,7 +39,7 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: if not circuit.observables_simultaneously_measurable: raise ValueError("Observables cannot be sampled simultaneously") for rt in circuit.result_types: - if isinstance(rt, ResultType.StateVector) or isinstance(rt, ResultType.Amplitude): + if isinstance(rt, (ResultType.Amplitude, ResultType.StateVector)): raise ValueError("StateVector or Amplitude cannot be specified when shots>0") elif isinstance(rt, ResultType.Probability): num_qubits = len(rt.target) or circuit.qubit_count diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index 628422c7e..ad2c701c6 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.py @@ -28,7 +28,8 @@ class CompilerDirective(Operator): """ def __init__(self, ascii_symbols: Sequence[str]): - """ + """Inits a `CompilerDirective`. + Args: ascii_symbols (Sequence[str]): ASCII string symbols for the compiler directiver. These are used when printing a diagram of circuits. @@ -97,7 +98,7 @@ def counterpart(self) -> CompilerDirective: f"Compiler directive {self.name} does not have counterpart implemented" ) - def __eq__(self, other): + def __eq__(self, other: CompilerDirective): return isinstance(other, CompilerDirective) and self.name == other.name def __repr__(self): diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 2533537f5..9376d338d 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -18,8 +18,7 @@ class StartVerbatimBox(CompilerDirective): - """ - Prevents the compiler from modifying any ensuing instructions + """Prevents the compiler from modifying any ensuing instructions until the appearance of a corresponding ``EndVerbatimBox``. """ @@ -37,8 +36,7 @@ def _to_openqasm(self) -> str: class EndVerbatimBox(CompilerDirective): - """ - Marks the end of a portion of code following a StartVerbatimBox that prevents the enclosed + """Marks the end of a portion of code following a StartVerbatimBox that prevents the enclosed instructions from being modified by the compiler. """ diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 907c495ce..453b121fd 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -28,14 +28,14 @@ class Gate(QuantumOperator): - """ - Class `Gate` represents a quantum gate that operates on N qubits. Gates are considered the + """Class `Gate` represents a quantum gate that operates on N qubits. Gates are considered the building blocks of quantum circuits. This class is considered the gate definition containing the metadata that defines what a gate is and what it does. """ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): - """ + """Initializes a `Gate`. + Args: qubit_count (Optional[int]): Number of qubits this gate interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for the gate. These are used when @@ -76,7 +76,7 @@ def to_ir( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Any: - """Returns IR object of quantum operator and target + r"""Returns IR object of quantum operator and target Args: target (QubitSet): target qubit(s). @@ -97,6 +97,7 @@ def to_ir( power (float): Integer or fractional power to raise the gate to. Negative powers will be split into an inverse, accompanied by the positive power. Default 1. + Returns: Any: IR object of the quantum operator and target @@ -128,8 +129,7 @@ def to_ir( raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self, target: QubitSet) -> Any: - """ - Returns the JAQCD representation of the gate. + """Returns the JAQCD representation of the gate. Args: target (QubitSet): target qubit(s). @@ -148,8 +148,7 @@ def _to_openqasm( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> str: - """ - Returns the openqasm string representation of the gate. + """Returns the OpenQASM string representation of the gate. Args: target (QubitSet): target qubit(s). @@ -208,7 +207,7 @@ def ascii_symbols(self) -> tuple[str, ...]: """tuple[str, ...]: Returns the ascii symbols for the quantum operator.""" return self._ascii_symbols - def __eq__(self, other): + def __eq__(self, other: Gate): return isinstance(other, Gate) and self.name == other.name def __repr__(self): diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 57013df4a..7617493cb 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -27,8 +27,7 @@ class GateCalibrations: - """ - An object containing gate calibration data. The data respresents the mapping on a particular gate + """An object containing gate calibration data. The data respresents the mapping on a particular gate on a set of qubits to its calibration to be used by a quantum device. This is represented by a dictionary with keys of `Tuple(Gate, QubitSet)` mapped to a `PulseSequence`. """ # noqa: E501 @@ -37,18 +36,18 @@ def __init__( self, pulse_sequences: dict[tuple[Gate, QubitSet], PulseSequence], ): - """ + """Inits a `GateCalibrations`. + Args: - pulse_sequences (dict[tuple[Gate, QubitSet], PulseSequence]): A mapping containing a key of - `(Gate, QubitSet)` mapped to the corresponding pulse sequence. + pulse_sequences (dict[tuple[Gate, QubitSet], PulseSequence]): A mapping containing a key + of `(Gate, QubitSet)` mapped to the corresponding pulse sequence. - """ # noqa: E501 + """ self.pulse_sequences: dict[tuple[Gate, QubitSet], PulseSequence] = pulse_sequences @property def pulse_sequences(self) -> dict[tuple[Gate, QubitSet], PulseSequence]: - """ - Gets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. + """Gets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. Returns: dict[tuple[Gate, QubitSet], PulseSequence]: The calibration data Dictionary. @@ -57,8 +56,7 @@ def pulse_sequences(self) -> dict[tuple[Gate, QubitSet], PulseSequence]: @pulse_sequences.setter def pulse_sequences(self, value: Any) -> None: - """ - Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. + """Sets the mapping of (Gate, Qubit) to the corresponding `PulseSequence`. Args: value(Any): The value for the pulse_sequences property to be set to. @@ -79,8 +77,7 @@ def pulse_sequences(self, value: Any) -> None: ) def copy(self) -> GateCalibrations: - """ - Returns a copy of the object. + """Returns a copy of the object. Returns: GateCalibrations: a copy of the calibrations. @@ -95,8 +92,7 @@ def filter( gates: list[Gate] | None = None, qubits: QubitSet | list[QubitSet] | None = None, ) -> GateCalibrations: - """ - Filters the data based on optional lists of gates and QubitSets. + """Filters the data based on optional lists of gates and QubitSets. Args: gates (list[Gate] | None): An optional list of gates to filter on. @@ -105,7 +101,7 @@ def filter( Returns: GateCalibrations: A filtered GateCalibrations object. - """ # noqa: E501 + """ keys = self.pulse_sequences.keys() if isinstance(qubits, QubitSet): qubits = [qubits] @@ -120,13 +116,15 @@ def filter( ) def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: - """ - Returns the defcal representation for the `GateCalibrations` object. + """Returns the defcal representation for the `GateCalibrations` object. Args: calibration_key (tuple[Gate, QubitSet] | None): An optional key to get a specific defcal. Default: None + Raises: + ValueError: Key does not exist in the `GateCalibrations` object. + Returns: str: the defcal string for the object. @@ -162,5 +160,5 @@ def _def_cal_gate(self, gate_key: tuple[Gate, QubitSet]) -> str: ] ) - def __eq__(self, other): + def __eq__(self, other: GateCalibrations): return isinstance(other, GateCalibrations) and other.pulse_sequences == self.pulse_sequences diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index e955c4bb0..df17c03c3 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -137,7 +137,7 @@ def h( Gate.register_gate(H) -class I(Gate): # noqa: E742, E261 +class I(Gate): # noqa: E742 r"""Identity gate. Unitary matrix: @@ -224,6 +224,9 @@ class GPhase(AngledGate): Args: angle (Union[FreeParameterExpression, float]): angle in radians. + + Raises: + ValueError: If `angle` is not present """ def __init__(self, angle: Union[FreeParameterExpression, float]): @@ -1066,8 +1069,9 @@ def _to_jaqcd(self, target: QubitSet, **kwargs) -> Any: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -1158,8 +1162,9 @@ def _to_jaqcd(self, target: QubitSet) -> Any: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -1435,8 +1440,9 @@ def _qasm_name(self) -> str: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ _theta = self.angle_1 _phi = self.angle_2 @@ -1928,8 +1934,9 @@ def _to_jaqcd(self, target: QubitSet) -> Any: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ cos = np.cos(self.angle / 2) sin = np.sin(self.angle / 2) @@ -2694,8 +2701,9 @@ def _to_jaqcd(self, target: QubitSet) -> Any: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) @@ -2806,8 +2814,9 @@ def _to_jaqcd(self, target: QubitSet) -> Any: def to_matrix(self) -> np.ndarray: r"""Returns a matrix representation of this gate. + Returns: - ndarray: The matrix representation of this gate. + np.ndarray: The matrix representation of this gate. """ cos = np.cos(self.angle / 2) isin = 1.0j * np.sin(self.angle / 2) @@ -3398,7 +3407,7 @@ class MS(TripleAngledGate): angle_1 (Union[FreeParameterExpression, float]): angle in radians. angle_2 (Union[FreeParameterExpression, float]): angle in radians. angle_3 (Union[FreeParameterExpression, float]): angle in radians. - Default value is angle_3=pi/2. + Default value is angle_3=pi/2. """ def __init__( @@ -3554,7 +3563,7 @@ def adjoint(self) -> list[Gate]: def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Unitary.construct( - targets=[qubit for qubit in target], + targets=list(target), matrix=Unitary._transform_matrix_to_ir(self._matrix), ) @@ -3571,7 +3580,7 @@ def _to_openqasm( return f"#pragma braket unitary({formatted_matrix}) {', '.join(qubits)}" - def __eq__(self, other): + def __eq__(self, other: Unitary): if isinstance(other, Unitary): return self.matrix_equivalence(other) return False @@ -3647,8 +3656,7 @@ def parameters(self) -> list[FreeParameter]: return list(self._pulse_sequence.parameters) def bind_values(self, **kwargs) -> PulseGate: - """ - Takes in parameters and returns an object with specified parameters + """Takes in parameters and returns an object with specified parameters replaced with their values. Returns: @@ -3681,7 +3689,7 @@ def pulse_gate( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """Arbitrary pulse gate which provides the ability to embed custom pulse sequences + r"""Arbitrary pulse gate which provides the ability to embed custom pulse sequences within circuits. Args: @@ -3721,8 +3729,7 @@ def pulse_gate( def format_complex(number: complex) -> str: - """ - Format a complex number into + im to be consumed by the braket unitary pragma + """Format a complex number into + im to be consumed by the braket unitary pragma Args: number (complex): A complex number. @@ -3736,8 +3743,7 @@ def format_complex(number: complex) -> str: return f"{number.real} {imag_sign} {abs(number.imag)}im" else: return f"{number.real}" + elif number.imag: + return f"{number.imag}im" else: - if number.imag: - return f"{number.imag}im" - else: - return "0" + return "0" diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index 0b2f90d01..bedfd0c44 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -29,8 +29,7 @@ class Instruction: - """ - An instruction is a quantum directive that describes the quantum task to perform on a quantum + """An instruction is a quantum directive that describes the quantum task to perform on a quantum device. """ @@ -43,8 +42,7 @@ def __init__( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """ - InstructionOperator includes objects of type `Gate` and `Noise` only. + """InstructionOperator includes objects of type `Gate` and `Noise` only. Args: operator (InstructionOperator): Operator for the instruction. @@ -110,30 +108,22 @@ def operator(self) -> InstructionOperator: @property def target(self) -> QubitSet: - """ - QubitSet: Target qubits that the operator is applied to. - """ + """QubitSet: Target qubits that the operator is applied to.""" return self._target @property def control(self) -> QubitSet: - """ - QubitSet: Target qubits that the operator is controlled on. - """ + """QubitSet: Target qubits that the operator is controlled on.""" return self._control @property def control_state(self) -> BasisState: - """ - BasisState: Quantum state that the operator is controlled to. - """ + """BasisState: Quantum state that the operator is controlled to.""" return self._control_state @property def power(self) -> float: - """ - float: Power that the operator is raised to. - """ + """float: Power that the operator is raised to.""" return self._power def adjoint(self) -> list[Instruction]: @@ -168,8 +158,7 @@ def to_ir( ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties | None = None, ) -> Any: - """ - Converts the operator into the canonical intermediate representation. + """Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. Args: @@ -209,8 +198,7 @@ def copy( control_state: Optional[BasisStateInput] = None, power: float = 1, ) -> Instruction: - """ - Return a shallow copy of the instruction. + """Return a shallow copy of the instruction. Note: If `target_mapping` is specified, then `self.target` is mapped to the specified @@ -282,7 +270,7 @@ def __repr__(self): f"'power': {self.power})" ) - def __eq__(self, other): + def __eq__(self, other: Instruction): if isinstance(other, Instruction): return ( self._operator, @@ -299,7 +287,7 @@ def __eq__(self, other): ) return NotImplemented - def __pow__(self, power, modulo=None): + def __pow__(self, power: float, modulo: float = None): new_power = self.power * power if modulo is not None: new_power %= modulo diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 6e87db78d..64a3556c1 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -27,8 +27,7 @@ class MomentType(str, Enum): - """ - The type of moments. + """The type of moments. GATE: a gate NOISE: a noise channel added directly to the circuit GATE_NOISE: a gate-based noise channel @@ -48,6 +47,7 @@ class MomentType(str, Enum): class MomentsKey(NamedTuple): """Key of the Moments mapping. + Args: time: moment qubits: qubit set @@ -65,8 +65,7 @@ class MomentsKey(NamedTuple): class Moments(Mapping[MomentsKey, Instruction]): - """ - An ordered mapping of `MomentsKey` or `NoiseMomentsKey` to `Instruction`. The + r"""An ordered mapping of `MomentsKey` or `NoiseMomentsKey` to `Instruction`. The core data structure that contains instructions, ordering they are inserted in, and time slices when they occur. `Moments` implements `Mapping` and functions the same as a read-only dictionary. It is mutable only through the `add()` method. @@ -77,7 +76,7 @@ class Moments(Mapping[MomentsKey, Instruction]): method. Args: - instructions (Iterable[Instruction], optional): Instructions to initialize self. + instructions (Iterable[Instruction] | None): Instructions to initialize self. Default = None. Examples: @@ -125,9 +124,8 @@ def qubit_count(self) -> int: @property def qubits(self) -> QubitSet: - """ - QubitSet: Get the qubits used across all of the instructions. The order of qubits is based - on the order in which the instructions were added. + """QubitSet: Get the qubits used across all of the instructions. The order of qubits is + based on the order in which the instructions were added. Note: Don't mutate this object, any changes may impact the behavior of this class and / or @@ -136,8 +134,7 @@ def qubits(self) -> QubitSet: return self._qubits def time_slices(self) -> dict[int, list[Instruction]]: - """ - Get instructions keyed by time. + """Get instructions keyed by time. Returns: dict[int, list[Instruction]]: Key is the time and value is a list of instructions that @@ -148,7 +145,6 @@ def time_slices(self) -> dict[int, list[Instruction]]: every call, with a computational runtime O(N) where N is the number of instructions in self. """ - time_slices = {} self.sort_moments() for key, instruction in self._moments.items(): @@ -161,8 +157,7 @@ def time_slices(self) -> dict[int, list[Instruction]]: def add( self, instructions: Union[Iterable[Instruction], Instruction], noise_index: int = 0 ) -> None: - """ - Add one or more instructions to self. + """Add one or more instructions to self. Args: instructions (Union[Iterable[Instruction], Instruction]): Instructions to add to self. @@ -218,6 +213,7 @@ def add_noise( self, instruction: Instruction, input_type: str = "noise", noise_index: int = 0 ) -> None: """Adds noise to a moment. + Args: instruction (Instruction): Instruction to add. input_type (str): One of MomentType. @@ -237,8 +233,7 @@ def add_noise( self._qubits.update(qubit_range) def sort_moments(self) -> None: - """ - Make the disordered moments in order. + """Make the disordered moments in order. 1. Make the readout noise in the end 2. Make the initialization noise at the beginning @@ -293,6 +288,7 @@ def items(self) -> ItemsView[MomentsKey, Instruction]: def values(self) -> ValuesView[Instruction]: """Return a view of self's instructions. + Returns: ValuesView[Instruction]: The (in-order) instructions. """ @@ -300,8 +296,7 @@ def values(self) -> ValuesView[Instruction]: return self._moments.values() def get(self, key: MomentsKey, default: Any | None = None) -> Instruction: - """ - Get the instruction in self by key. + """Get the instruction in self by key. Args: key (MomentsKey): Key of the instruction to fetch. @@ -312,7 +307,7 @@ def get(self, key: MomentsKey, default: Any | None = None) -> Instruction: """ return self._moments.get(key, default) - def __getitem__(self, key): + def __getitem__(self, key: MomentsKey): return self._moments.__getitem__(key) def __iter__(self): @@ -321,15 +316,15 @@ def __iter__(self): def __len__(self): return self._moments.__len__() - def __contains__(self, item): + def __contains__(self, item: MomentsKey): return self._moments.__contains__(item) - def __eq__(self, other): + def __eq__(self, other: Moments): if isinstance(other, Moments): return self._moments == other._moments return NotImplemented - def __ne__(self, other): + def __ne__(self, other: Moments): result = self.__eq__(other) if result is not NotImplemented: return not result diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index af1bb422f..fe256f1da 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import Any, Optional, Union +from typing import Any, ClassVar, Optional, Union import numpy as np @@ -31,8 +31,7 @@ class Noise(QuantumOperator): - """ - Class `Noise` represents a noise channel that operates on one or multiple qubits. Noise + """Class `Noise` represents a noise channel that operates on one or multiple qubits. Noise are considered as building blocks of quantum circuits that simulate noise. It can be used as an operator in an `Instruction` object. It appears in the diagram when user prints a circuit with `Noise`. This class is considered the noise channel definition containing @@ -40,7 +39,8 @@ class Noise(QuantumOperator): """ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): - """ + """Initializes a `Noise` object. + Args: qubit_count (Optional[int]): Number of qubits this noise channel interacts with. ascii_symbols (Sequence[str]): ASCII string symbols for this noise channel. These @@ -56,8 +56,7 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): @property def name(self) -> str: - """ - Returns the name of the quantum operator + """Returns the name of the quantum operator Returns: str: The name of the quantum operator as a string @@ -79,6 +78,7 @@ def to_ir( serialization_properties (SerializationProperties | None): The serialization properties to use while serializing the object to the IR representation. The serialization properties supplied must correspond to the supplied `ir_type`. Defaults to None. + Returns: Any: IR object of the quantum operator and target @@ -103,8 +103,7 @@ def to_ir( raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self, target: QubitSet) -> Any: - """ - Returns the JAQCD representation of the noise. + """Returns the JAQCD representation of the noise. Args: target (QubitSet): target qubit(s). @@ -117,8 +116,7 @@ def _to_jaqcd(self, target: QubitSet) -> Any: def _to_openqasm( self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties ) -> str: - """ - Returns the openqasm string representation of the noise. + """Returns the openqasm string representation of the noise. Args: target (QubitSet): target qubit(s). @@ -138,7 +136,7 @@ def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: """ raise NotImplementedError("to_matrix has not been implemented yet.") - def __eq__(self, other): + def __eq__(self, other: Noise): if isinstance(other, Noise): return self.name == other.name return False @@ -148,8 +146,8 @@ def __repr__(self): @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of + this class. Args: noise (dict): A dictionary representation of an object of this class. @@ -166,6 +164,7 @@ def from_dict(cls, noise: dict) -> Noise: @classmethod def register_noise(cls, noise: type[Noise]) -> None: """Register a noise implementation by adding it into the Noise class. + Args: noise (type[Noise]): Noise class to register. """ @@ -173,8 +172,7 @@ def register_noise(cls, noise: type[Noise]) -> None: class SingleProbabilisticNoise(Noise, Parameterizable): - """ - Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits + """Class `SingleProbabilisticNoise` represents the bit/phase flip noise channel on N qubits parameterized by a single probability. """ @@ -185,7 +183,8 @@ def __init__( ascii_symbols: Sequence[str], max_probability: float = 0.5, ): - """ + """Initializes a `SingleProbabilisticNoise`. + Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. @@ -209,6 +208,7 @@ def __init__( @property def probability(self) -> float: """The probability that parametrizes the noise channel. + Returns: float: The probability that parametrizes the noise channel. """ @@ -222,9 +222,8 @@ def __str__(self): @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions @@ -232,14 +231,13 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: """ return [self._probability] - def __eq__(self, other): - if isinstance(other, type(self)): + def __eq__(self, other: SingleProbabilisticNoise): + if isinstance(other, SingleProbabilisticNoise): return self.name == other.name and self.probability == other.probability return False def bind_values(self, **kwargs) -> SingleProbabilisticNoise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: SingleProbabilisticNoise: A new Noise object of the same type with the requested @@ -251,8 +249,7 @@ def bind_values(self, **kwargs) -> SingleProbabilisticNoise: raise NotImplementedError def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. + """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back @@ -267,8 +264,7 @@ def to_dict(self) -> dict: class SingleProbabilisticNoise_34(SingleProbabilisticNoise): - """ - Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise + """Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise channels parameterized by a single probability. """ @@ -278,7 +274,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Initializes a `SingleProbabilisticNoise_34`. + Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. @@ -301,8 +298,7 @@ def __init__( class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): - """ - Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel + """Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel parameterized by a single probability. """ @@ -312,7 +308,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Initializes a `SingleProbabilisticNoise_1516`. + Args: probability (Union[FreeParameterExpression, float]): The probability that the noise occurs. @@ -335,12 +332,11 @@ def __init__( class MultiQubitPauliNoise(Noise, Parameterizable): - """ - Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, + """Class `MultiQubitPauliNoise` represents a general multi-qubit Pauli channel, parameterized by up to 4**N - 1 probabilities. """ - _allowed_substrings = {"I", "X", "Y", "Z"} + _allowed_substrings: ClassVar = {"I", "X", "Y", "Z"} def __init__( self, @@ -372,7 +368,6 @@ def __init__( TypeError: If the type of the dictionary keys are not strings. If the probabilities are not floats. """ - super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) self._probabilities = probabilities @@ -395,10 +390,8 @@ def __init__( total_prob += prob if not (1.0 >= total_prob >= 0.0): raise ValueError( - ( - "Total probability must be a real number in the interval [0, 1]. " - f"Total probability was {total_prob}." - ) + "Total probability must be a real number in the interval [0, 1]. " + f"Total probability was {total_prob}." ) @classmethod @@ -409,17 +402,13 @@ def _validate_pauli_string( raise TypeError(f"Type of {pauli_str} was not a string.") if len(pauli_str) != qubit_count: raise ValueError( - ( - "Length of each Pauli string must be equal to number of qubits. " - f"{pauli_str} had length {len(pauli_str)} instead of length {qubit_count}." - ) + "Length of each Pauli string must be equal to number of qubits. " + f"{pauli_str} had length {len(pauli_str)} instead of length {qubit_count}." ) if not set(pauli_str) <= allowed_substrings: raise ValueError( - ( - "Strings must be Pauli strings consisting of only [I, X, Y, Z]. " - f"Received {pauli_str}." - ) + "Strings must be Pauli strings consisting of only [I, X, Y, Z]. " + f"Received {pauli_str}." ) def __repr__(self): @@ -431,14 +420,15 @@ def __repr__(self): def __str__(self): return f"{self.name}({self._probabilities})" - def __eq__(self, other): - if isinstance(other, type(self)): + def __eq__(self, other: MultiQubitPauliNoise): + if isinstance(other, MultiQubitPauliNoise): return self.name == other.name and self._probabilities == other._probabilities return False @property def probabilities(self) -> dict[str, float]: """A map of a Pauli string to its corresponding probability. + Returns: dict[str, float]: A map of a Pauli string to its corresponding probability. """ @@ -446,9 +436,8 @@ def probabilities(self) -> dict[str, float]: @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. Parameters are in alphabetical order of the Pauli strings in `probabilities`. @@ -461,8 +450,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: ] def bind_values(self, **kwargs) -> MultiQubitPauliNoise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: MultiQubitPauliNoise: A new Noise object of the same type with the requested @@ -474,14 +462,13 @@ def bind_values(self, **kwargs) -> MultiQubitPauliNoise: raise NotImplementedError def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. + """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ - probabilities = dict() + probabilities = {} for pauli_string, prob in self._probabilities.items(): probabilities[pauli_string] = _parameter_to_dict(prob) return { @@ -493,8 +480,7 @@ def to_dict(self) -> dict: class PauliNoise(Noise, Parameterizable): - """ - Class `PauliNoise` represents the a single-qubit Pauli noise channel + """Class `PauliNoise` represents the a single-qubit Pauli noise channel acting on one qubit. It is parameterized by three probabilities. """ @@ -506,7 +492,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Initializes a `PauliNoise`. + Args: probX (Union[FreeParameterExpression, float]): The X coefficient of the Kraus operators in the channel. @@ -556,7 +543,8 @@ def _get_param_float(param: Union[FreeParameterExpression, float], param_name: s @property def probX(self) -> Union[FreeParameterExpression, float]: - """ + """The probability of a Pauli X error. + Returns: Union[FreeParameterExpression, float]: The probability of a Pauli X error. """ @@ -564,7 +552,8 @@ def probX(self) -> Union[FreeParameterExpression, float]: @property def probY(self) -> Union[FreeParameterExpression, float]: - """ + """The probability of a Pauli Y error. + Returns: Union[FreeParameterExpression, float]: The probability of a Pauli Y error. """ @@ -572,7 +561,8 @@ def probY(self) -> Union[FreeParameterExpression, float]: @property def probZ(self) -> Union[FreeParameterExpression, float]: - """ + """The probability of a Pauli Z error. + Returns: Union[FreeParameterExpression, float]: The probability of a Pauli Z error. """ @@ -591,16 +581,15 @@ def __repr__(self): def __str__(self): return f"{self.name}({self._parameters[0]}, {self._parameters[1]}, {self._parameters[2]})" - def __eq__(self, other): - if isinstance(other, type(self)): + def __eq__(self, other: PauliNoise): + if isinstance(other, PauliNoise): return self.name == other.name and self._parameters == other._parameters return False @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. Parameters are in the order [probX, probY, probZ] @@ -611,8 +600,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: return self._parameters def bind_values(self, **kwargs) -> PauliNoise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: PauliNoise: A new Noise object of the same type with the requested @@ -624,8 +612,7 @@ def bind_values(self, **kwargs) -> PauliNoise: raise NotImplementedError def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. + """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back @@ -642,8 +629,7 @@ def to_dict(self) -> dict: class DampingNoise(Noise, Parameterizable): - """ - Class `DampingNoise` represents a damping noise channel + """Class `DampingNoise` represents a damping noise channel on N qubits parameterized by gamma. """ @@ -653,7 +639,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Initalizes a `DampingNoise`. + Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. qubit_count (Optional[int]): The number of qubits to apply noise. @@ -661,7 +648,7 @@ def __init__( printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. - Raises: + Raises: ValueError: If `qubit_count` < 1, `ascii_symbols` is `None`, @@ -678,6 +665,7 @@ def __init__( @property def gamma(self) -> float: """Probability of damping. + Returns: float: Probability of damping. """ @@ -691,9 +679,8 @@ def __str__(self): @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. Returns: list[Union[FreeParameterExpression, float]]: The free parameter expressions @@ -701,14 +688,13 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: """ return [self._gamma] - def __eq__(self, other): - if isinstance(other, type(self)): + def __eq__(self, other: DampingNoise): + if isinstance(other, DampingNoise): return self.name == other.name and self.gamma == other.gamma return False def bind_values(self, **kwargs) -> DampingNoise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: DampingNoise: A new Noise object of the same type with the requested @@ -720,8 +706,7 @@ def bind_values(self, **kwargs) -> DampingNoise: raise NotImplementedError def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. + """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back @@ -736,8 +721,7 @@ def to_dict(self) -> dict: class GeneralizedAmplitudeDampingNoise(DampingNoise): - """ - Class `GeneralizedAmplitudeDampingNoise` represents the generalized amplitude damping + """Class `GeneralizedAmplitudeDampingNoise` represents the generalized amplitude damping noise channel on N qubits parameterized by gamma and probability. """ @@ -748,7 +732,8 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """ + """Inits a `GeneralizedAmplitudeDampingNoise`. + Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. probability (Union[FreeParameterExpression, float]): Probability of the system being @@ -758,7 +743,7 @@ def __init__( printing a diagram of a circuit. The length must be the same as `qubit_count`, and index ordering is expected to correlate with the target ordering on the instruction. - Raises: + Raises: ValueError: If `qubit_count` < 1, `ascii_symbols` is `None`, @@ -776,6 +761,7 @@ def __init__( @property def probability(self) -> float: """Probability of the system being excited by the environment. + Returns: float: Probability of the system being excited by the environment. """ @@ -793,9 +779,8 @@ def __str__(self): @property def parameters(self) -> list[Union[FreeParameterExpression, float]]: - """ - Returns the parameters associated with the object, either unbound free parameter expressions - or bound values. + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. Parameters are in the order [gamma, probability] @@ -805,8 +790,8 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: """ return [self.gamma, self.probability] - def __eq__(self, other): - if isinstance(other, type(self)): + def __eq__(self, other: GeneralizedAmplitudeDampingNoise): + if isinstance(other, GeneralizedAmplitudeDampingNoise): return ( self.name == other.name and self.gamma == other.gamma @@ -815,8 +800,7 @@ def __eq__(self, other): return False def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. + """Converts this object into a dictionary representation. Returns: dict: A dictionary object that represents this object. It can be converted back diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 06c0b3620..6dda8130d 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -32,6 +32,7 @@ def no_noise_applied_warning(noise_applied: bool) -> None: """Helper function to give a warning is noise is not applied. + Args: noise_applied (bool): True if the noise has been applied. """ @@ -39,12 +40,14 @@ def no_noise_applied_warning(noise_applied: bool) -> None: warnings.warn( "Noise is not applied to any gate, as there is no eligible gate in the circuit" " with the input criteria or there is no multi-qubit gate to apply" - " the multi-qubit noise." + " the multi-qubit noise.", + stacklevel=1, ) def wrap_with_list(an_item: Any) -> list[Any]: """Helper function to make the input parameter a list. + Args: an_item (Any): The item to wrap. @@ -61,6 +64,7 @@ def check_noise_target_gates(noise: Noise, target_gates: Iterable[type[Gate]]) - 1. whether all the elements in target_gates are a Gate type; 2. if `noise` is multi-qubit noise and `target_gates` contain gates with the number of qubits is the same as `noise.qubit_count`. + Args: noise (Noise): A Noise class object to be applied to the circuit. target_gates (Iterable[type[Gate]]): Gate class or @@ -93,7 +97,6 @@ def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray) -> None noise (Noise): A Noise class object to be applied to the circuit. target_unitary (ndarray): matrix of the target unitary gates """ - if not isinstance(target_unitary, np.ndarray): raise TypeError("target_unitary must be a np.ndarray type") @@ -104,11 +107,12 @@ def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray) -> None def check_noise_target_qubits( circuit: Circuit, target_qubits: Optional[QubitSetInput] = None ) -> QubitSet: - """ - Helper function to check whether all the target_qubits are positive integers. + """Helper function to check whether all the target_qubits are positive integers. + Args: circuit (Circuit): A ciruit where `noise` is to be checked. target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). + Returns: QubitSet: The target qubits. """ @@ -129,8 +133,7 @@ def check_noise_target_qubits( def apply_noise_to_moments( circuit: Circuit, noise: Iterable[type[Noise]], target_qubits: QubitSet, position: str ) -> Circuit: - """ - Apply initialization/readout noise to the circuit. + """Apply initialization/readout noise to the circuit. When `noise.qubit_count` == 1, `noise` is added to all qubits in `target_qubits`. @@ -210,7 +213,6 @@ def _apply_noise_to_gates_helper( noise_index: The number of noise channels applied to the gate noise_applied: Whether noise is applied or not """ - for noise_channel in noise: if noise_channel.qubit_count == 1: for qubit in intersection: @@ -218,17 +220,16 @@ def _apply_noise_to_gates_helper( noise_index += 1 new_noise_instruction.append((Instruction(noise_channel, qubit), noise_index)) noise_applied = True - else: - # only apply noise to the gates that have the same qubit_count as the noise. - if ( - instruction.operator.qubit_count == noise_channel.qubit_count - and instruction.target.issubset(target_qubits) - ): - noise_index += 1 - new_noise_instruction.append( - (Instruction(noise_channel, instruction.target), noise_index) - ) - noise_applied = True + # only apply noise to the gates that have the same qubit_count as the noise. + elif ( + instruction.operator.qubit_count == noise_channel.qubit_count + and instruction.target.issubset(target_qubits) + ): + noise_index += 1 + new_noise_instruction.append( + (Instruction(noise_channel, instruction.target), noise_index) + ) + noise_applied = True return new_noise_instruction, noise_index, noise_applied @@ -265,7 +266,6 @@ def apply_noise_to_gates( If no `target_gates` exist in `target_qubits` or in the whole circuit when `target_qubits` is not given. """ - new_moments = Moments() noise_applied = False diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py index 170d3e996..1db40aa5f 100644 --- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py +++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py @@ -29,6 +29,9 @@ def instruction_matches(self, instruction: Instruction) -> bool: Args: instruction (Instruction): An Instruction to match. + Raises: + NotImplementedError: Not implemented. + Returns: bool: True if an Instruction matches the criteria. """ @@ -38,8 +41,7 @@ def instruction_matches(self, instruction: Instruction) -> bool: def _check_target_in_qubits( qubits: Optional[set[Union[int, tuple[int]]]], target: QubitSetInput ) -> bool: - """ - Returns true if the given targets of an instruction match the given qubit input set. + """Returns true if the given targets of an instruction match the given qubit input set. Args: qubits (Optional[set[Union[int, tuple[int]]]]): The qubits provided to the criteria. diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py index b9f0d2cc4..63625491f 100644 --- a/src/braket/circuits/noise_model/criteria.py +++ b/src/braket/circuits/noise_model/criteria.py @@ -20,8 +20,8 @@ class CriteriaKey(str, Enum): - """ - Specifies the types of keys that a criteria may use to match an instruction, observable, etc. + """Specifies the types of keys that a criteria may use to match an instruction, + observable, etc. """ QUBIT = "QUBIT" @@ -31,8 +31,7 @@ class CriteriaKey(str, Enum): class CriteriaKeyResult(str, Enum): - """ - The get_keys() method may return this enum instead of actual keys for + """The get_keys() method may return this enum instead of actual keys for a given criteria key type. """ @@ -90,8 +89,8 @@ def to_dict(self) -> dict: @classmethod def from_dict(cls, criteria: dict) -> Criteria: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of this + class. Args: criteria (dict): A dictionary representation of an object of this class. diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py index 11f24619e..bc86e53bf 100644 --- a/src/braket/circuits/noise_model/criteria_input_parsing.py +++ b/src/braket/circuits/noise_model/criteria_input_parsing.py @@ -19,10 +19,9 @@ def parse_operator_input( - operators: Union[QuantumOperator, Iterable[QuantumOperator]] + operators: Union[QuantumOperator, Iterable[QuantumOperator]], ) -> Optional[set[QuantumOperator]]: - """ - Processes the quantum operator input to __init__ to validate and return a set of + """Processes the quantum operator input to __init__ to validate and return a set of QuantumOperators. Args: @@ -49,8 +48,7 @@ def parse_operator_input( def parse_qubit_input( qubits: Optional[QubitSetInput], expected_qubit_count: Optional[int] = 0 ) -> Optional[set[Union[int, tuple[int]]]]: - """ - Processes the qubit input to __init__ to validate and return a set of qubit targets. + """Processes the qubit input to __init__ to validate and return a set of qubit targets. Args: qubits (Optional[QubitSetInput]): Qubit input. diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py index 623c02477..7870e9b6b 100644 --- a/src/braket/circuits/noise_model/gate_criteria.py +++ b/src/braket/circuits/noise_model/gate_criteria.py @@ -33,8 +33,7 @@ def __init__( gates: Optional[Union[Gate, Iterable[Gate]]] = None, qubits: Optional[QubitSetInput] = None, ): - """ - Creates Gate-based Criteria. See instruction_matches() for more details. + """Creates Gate-based Criteria. See instruction_matches() for more details. Args: gates (Optional[Union[Gate, Iterable[Gate]]]): A set of relevant Gates. All the Gates @@ -60,7 +59,8 @@ def __repr__(self): return f"{self.__class__.__name__}(gates={gate_names}, qubits={self._qubits})" def applicable_key_types(self) -> Iterable[CriteriaKey]: - """ + """Returns an Iterable of criteria keys. + Returns: Iterable[CriteriaKey]: This Criteria operates on Gates and Qubits. """ @@ -86,8 +86,8 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: return set() def to_dict(self) -> dict: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of this + class. Returns: dict: A dictionary representing the serialized version of this Criteria. diff --git a/src/braket/circuits/noise_model/initialization_criteria.py b/src/braket/circuits/noise_model/initialization_criteria.py index e40d4e9de..4bf29a356 100644 --- a/src/braket/circuits/noise_model/initialization_criteria.py +++ b/src/braket/circuits/noise_model/initialization_criteria.py @@ -18,14 +18,11 @@ class InitializationCriteria(Criteria): - """ - Criteria that implement these methods may be used to determine initialization noise. - """ + """Criteria that implement these methods may be used to determine initialization noise.""" @abstractmethod def qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput: - """ - Returns subset of passed qubits that match the criteria. + """Returns subset of passed qubits that match the criteria. Args: qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 8922cda72..4ea566131 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -56,8 +56,8 @@ def to_dict(self) -> dict: @classmethod def from_dict(cls, noise_model_item: dict) -> NoiseModelInstruction: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of + this class. Args: noise_model_item (dict): A dictionary representation of an object of this class. @@ -82,8 +82,7 @@ class NoiseModelInstructions: class NoiseModel: - """ - A Noise Model can be thought of as a set of Noise objects, and a corresponding set of + """A Noise Model can be thought of as a set of Noise objects, and a corresponding set of criteria for how each Noise object should be applied to a circuit. For example, a noise model may represent that every H gate that acts on qubit 0 has a 10% probability of a bit flip, and every X gate that acts on qubit 0 has a 20% probability of a bit flip, and 5% probability of @@ -110,8 +109,7 @@ def __str__(self): @property def instructions(self) -> list[NoiseModelInstruction]: - """ - List all the noise in the NoiseModel. + """List all the noise in the NoiseModel. Returns: list[NoiseModelInstruction]: The noise model instructions. @@ -157,8 +155,7 @@ def _add_instruction(self, instruction: NoiseModelInstruction) -> NoiseModel: return self def remove_noise(self, index: int) -> NoiseModel: - """ - Removes the noise and corresponding criteria from the NoiseModel at the given index. + """Removes the noise and corresponding criteria from the NoiseModel at the given index. Args: index (int): The index of the instruction to remove. @@ -175,6 +172,7 @@ def remove_noise(self, index: int) -> NoiseModel: def get_instructions_by_type(self) -> NoiseModelInstructions: """Returns the noise in this model by noise type. + Returns: NoiseModelInstructions: The noise model instructions. """ @@ -200,8 +198,7 @@ def from_filter( gate: Optional[Gate] = None, noise: Optional[type[Noise]] = None, ) -> NoiseModel: - """ - Returns a new NoiseModel from this NoiseModel using a given filter. If no filters are + """Returns a new NoiseModel from this NoiseModel using a given filter. If no filters are specified, the returned NoiseModel will be the same as this one. Args: @@ -235,8 +232,7 @@ class as the given noise class. return new_model def apply(self, circuit: Circuit) -> Circuit: - """ - Applies this noise model to a circuit, and returns a new circuit that's the `noisy` + """Applies this noise model to a circuit, and returns a new circuit that's the `noisy` version of the given circuit. If multiple noise will act on the same instruction, they will be applied in the order they are added to the noise model. @@ -261,8 +257,7 @@ def _apply_gate_noise( circuit: Circuit, gate_noise_instructions: list[NoiseModelInstruction], ) -> Circuit: - """ - Applies the gate noise to return a new circuit that's the `noisy` version of the given + """Applies the gate noise to return a new circuit that's the `noisy` version of the given circuit. Args: @@ -295,8 +290,8 @@ def _apply_init_noise( circuit: Circuit, init_noise_instructions: list[NoiseModelInstruction], ) -> Circuit: - """ - Applies the initialization noise of this noise model to a circuit and returns the circuit. + """Applies the initialization noise of this noise model to a circuit and returns + the circuit. Args: circuit (Circuit): A circuit to apply `noise` to. @@ -320,8 +315,7 @@ def _apply_readout_noise( circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction], ) -> Circuit: - """ - Applies the readout noise of this noise model to a circuit and returns the circuit. + """Applies the readout noise of this noise model to a circuit and returns the circuit. Args: circuit (Circuit): A circuit to apply `noise` to. @@ -339,8 +333,7 @@ def _apply_readout_noise( def _items_to_string( cls, instructions_title: str, instructions: list[NoiseModelInstruction] ) -> list[str]: - """ - Creates a string representation of a list of instructions. + """Creates a string representation of a list of instructions. Args: instructions_title (str): The title for this list of instructions. @@ -358,8 +351,8 @@ def _items_to_string( @classmethod def from_dict(cls, noise_dict: dict) -> NoiseModel: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance + of this class. Args: noise_dict (dict): A dictionary representation of an object of this class. diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py index 2275ccce7..1a2126502 100644 --- a/src/braket/circuits/noise_model/observable_criteria.py +++ b/src/braket/circuits/noise_model/observable_criteria.py @@ -33,8 +33,7 @@ def __init__( observables: Optional[Union[Observable, Iterable[Observable]]] = None, qubits: Optional[QubitSetInput] = None, ): - """ - Creates Observable-based Criteria. See instruction_matches() for more details. + """Creates Observable-based Criteria. See instruction_matches() for more details. Args: observables (Optional[Union[Observable, Iterable[Observable]]]): A set of relevant @@ -66,7 +65,8 @@ def __repr__(self): return f"{self.__class__.__name__}(observables={observables_names}, qubits={self._qubits})" def applicable_key_types(self) -> Iterable[CriteriaKey]: - """ + """Returns an Iterable of criteria keys. + Returns: Iterable[CriteriaKey]: This Criteria operates on Observables and Qubits. """ @@ -93,8 +93,8 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: return set() def to_dict(self) -> dict: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of + this class. Returns: dict: A dictionary representing the serialized version of this Criteria. @@ -116,6 +116,7 @@ def result_type_matches(self, result_type: ResultType) -> bool: Args: result_type (ResultType): A result type or list of result types to match. + Returns: bool: Returns true if the result type is one of the Observables provided in the constructor and the target is a qubit (or set of qubits)provided in the constructor. diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index abed13af8..eb8ea0f21 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -24,8 +24,7 @@ class QubitInitializationCriteria(InitializationCriteria): """This class models initialization noise Criteria based on qubits.""" def __init__(self, qubits: Optional[QubitSetInput] = None): - """ - Creates initialization noise Qubit-based Criteria. + """Creates initialization noise Qubit-based Criteria. Args: qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits @@ -40,7 +39,8 @@ def __repr__(self): return f"{self.__class__.__name__}(qubits={self._qubits})" def applicable_key_types(self) -> Iterable[CriteriaKey]: - """ + """Gets the QUBIT criteria key. + Returns: Iterable[CriteriaKey]: This Criteria operates on Qubits, but is valid for all Gates. """ @@ -65,8 +65,8 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: return set() def to_dict(self) -> dict: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of + this class. Returns: dict: A dictionary representing the serialized version of this Criteria. @@ -78,8 +78,7 @@ def to_dict(self) -> dict: } def qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput: - """ - Returns subset of passed qubits that match the criteria. + """Returns subset of passed qubits that match the criteria. Args: qubits (QubitSetInput): A qubit or set of qubits that may match the criteria. @@ -94,8 +93,7 @@ def qubit_intersection(self, qubits: QubitSetInput) -> QubitSetInput: @classmethod def from_dict(cls, criteria: dict) -> Criteria: - """ - Deserializes a dictionary into a Criteria object. + """Deserializes a dictionary into a Criteria object. Args: criteria (dict): A dictionary representation of a QubitCriteria. diff --git a/src/braket/circuits/noise_model/result_type_criteria.py b/src/braket/circuits/noise_model/result_type_criteria.py index 77c8d0d68..4d52c5c29 100644 --- a/src/braket/circuits/noise_model/result_type_criteria.py +++ b/src/braket/circuits/noise_model/result_type_criteria.py @@ -26,6 +26,7 @@ def result_type_matches(self, result_type: ResultType) -> bool: Args: result_type (ResultType): A result type or list of result types to match. + Returns: bool: True if the result type matches the criteria. """ diff --git a/src/braket/circuits/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py index 229fc11ba..34b348e2e 100644 --- a/src/braket/circuits/noise_model/unitary_gate_criteria.py +++ b/src/braket/circuits/noise_model/unitary_gate_criteria.py @@ -26,14 +26,14 @@ class UnitaryGateCriteria(CircuitInstructionCriteria): """This class models noise Criteria based on unitary gates represented as a matrix.""" def __init__(self, unitary: Unitary, qubits: Optional[QubitSetInput] = None): - """ - Creates unitary gate-based Criteria. See instruction_matches() for more details. + """Creates unitary gate-based Criteria. See instruction_matches() for more details. Args: unitary (Unitary): A unitary gate matrix represented as a Braket Unitary. qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits are provided, all (possible) qubits are considered to be relevant. - Throws: + + Raises: ValueError: If unitary is not a Unitary type. """ if not isinstance(unitary, Unitary): @@ -48,7 +48,8 @@ def __repr__(self): return f"{self.__class__.__name__}(unitary={self._unitary}, qubits={self._qubits})" def applicable_key_types(self) -> Iterable[CriteriaKey]: - """ + """Returns keys based on criterion. + Returns: Iterable[CriteriaKey]: This Criteria operates on unitary gates and Qubits. """ @@ -75,8 +76,8 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: return set() def to_dict(self) -> dict: - """ - Converts a dictionary representing an object of this class into an instance of this class. + """Converts a dictionary representing an object of this class into an instance of + this class. Returns: dict: A dictionary representing the serialized version of this Criteria. diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 572489a9d..904f7ac74 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -13,7 +13,7 @@ import itertools from collections.abc import Iterable -from typing import Any, Union +from typing import Any, ClassVar, Union import numpy as np @@ -52,7 +52,7 @@ class BitFlip(SingleProbabilisticNoise): - """Bit flip noise channel which transforms a density matrix :math:`\\rho` according to: + r"""Bit flip noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} @@ -96,6 +96,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -127,9 +128,11 @@ def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction] for qubit in QubitSet(target) ] - def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Noise: + """Takes in parameters and attempts to assign them to values. + + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. Returns: Noise: A new Noise object of the same type with the requested @@ -139,8 +142,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -155,7 +157,7 @@ def from_dict(cls, noise: dict) -> Noise: class PhaseFlip(SingleProbabilisticNoise): - """Phase flip noise channel which transforms a density matrix :math:`\\rho` according to: + r"""Phase flip noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-p) \\rho + p X \\rho X^{\\dagger} @@ -199,6 +201,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -230,9 +233,11 @@ def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instructio for qubit in QubitSet(target) ] - def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Noise: + """Takes in parameters and attempts to assign them to values. + + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. Returns: Noise: A new Noise object of the same type with the requested @@ -242,8 +247,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -258,7 +262,7 @@ def from_dict(cls, noise: dict) -> Noise: class PauliChannel(PauliNoise): - """Pauli noise channel which transforms a density matrix :math:`\\rho` according to: + r"""Pauli noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-probX-probY-probZ) \\rho @@ -307,6 +311,7 @@ def __init__( probZ: Union[FreeParameterExpression, float], ): """Creates PauliChannel noise. + Args: probX (Union[FreeParameterExpression, float]): X rotation probability. probY (Union[FreeParameterExpression, float]): Y rotation probability. @@ -336,6 +341,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -376,8 +382,7 @@ def pauli_channel( ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -391,8 +396,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -411,7 +415,7 @@ def from_dict(cls, noise: dict) -> Noise: class Depolarizing(SingleProbabilisticNoise_34): - """Depolarizing noise channel which transforms a density matrix :math:`\\rho` according to: + r"""Depolarizing noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow (1-p) \\rho @@ -473,6 +477,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -507,8 +512,7 @@ def depolarizing(target: QubitSetInput, probability: float) -> Iterable[Instruct ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -518,8 +522,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -534,7 +537,7 @@ def from_dict(cls, noise: dict) -> Noise: class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): - """Two-Qubit Depolarizing noise channel which transforms a + r"""Two-Qubit Depolarizing noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: @@ -605,10 +608,10 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ - SI = np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex) SX = np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) SY = np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -652,8 +655,7 @@ def two_qubit_depolarizing( ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -663,8 +665,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -679,7 +680,7 @@ def from_dict(cls, noise: dict) -> Noise: class TwoQubitDephasing(SingleProbabilisticNoise_34): - """Two-Qubit Dephasing noise channel which transforms a + r"""Two-Qubit Dephasing noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: @@ -732,6 +733,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -771,8 +773,7 @@ def two_qubit_dephasing( ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -782,8 +783,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -798,7 +798,7 @@ def from_dict(cls, noise: dict) -> Noise: class TwoQubitPauliChannel(MultiQubitPauliNoise): - """Two-Qubit Pauli noise channel which transforms a + r"""Two-Qubit Pauli noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: @@ -855,14 +855,14 @@ class TwoQubitPauliChannel(MultiQubitPauliNoise): This noise channel is shown as `PC_2({"pauli_string": probability})` in circuit diagrams. """ - _paulis = { + _paulis: ClassVar = { "I": np.array([[1.0, 0.0], [0.0, 1.0]], dtype=complex), "X": np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex), "Y": np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex), "Z": np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex), } _tensor_products_strings = itertools.product(_paulis.keys(), repeat=2) - _names_list = ["".join(x) for x in _tensor_products_strings] + _names_list: ClassVar = ["".join(x) for x in _tensor_products_strings] def __init__(self, probabilities: dict[str, float]): super().__init__( @@ -877,6 +877,7 @@ def __init__(self, probabilities: dict[str, float]): def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -930,8 +931,7 @@ def two_qubit_pauli_channel( ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -945,8 +945,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -954,7 +953,7 @@ def from_dict(cls, noise: dict) -> Noise: Returns: Noise: A Noise object that represents the passed in dictionary. """ - probabilities = dict() + probabilities = {} for pauli_string, prob in noise["probabilities"].items(): probabilities[pauli_string] = _parameter_from_dict(prob) return TwoQubitPauliChannel(probabilities=probabilities) @@ -964,7 +963,7 @@ def from_dict(cls, noise: dict) -> Noise: class AmplitudeDamping(DampingNoise): - """AmplitudeDamping noise channel which transforms a density matrix :math:`\\rho` according to: + r"""AmplitudeDamping noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow E_0 \\rho E_0^{\\dagger} + E_1 \\rho E_1^{\\dagger} @@ -1006,6 +1005,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -1038,8 +1038,7 @@ def amplitude_damping(target: QubitSetInput, gamma: float) -> Iterable[Instructi ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -1049,8 +1048,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -1065,7 +1063,7 @@ def from_dict(cls, noise: dict) -> Noise: class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): - """Generalized AmplitudeDamping noise channel which transforms a + r"""Generalized AmplitudeDamping noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow E_0 \\rho E_0^{\\dagger} + E_1 \\rho E_1^{\\dagger} @@ -1131,6 +1129,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -1175,8 +1174,7 @@ def generalized_amplitude_damping( ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -1188,8 +1186,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -1207,7 +1204,7 @@ def from_dict(cls, noise: dict) -> Noise: class PhaseDamping(DampingNoise): - """Phase damping noise channel which transforms a density matrix :math:`\\rho` according to: + r"""Phase damping noise channel which transforms a density matrix :math:`\\rho` according to: .. math:: \\rho \\Rightarrow E_0 \\rho E_0^{\\dagger} + E_1 \\rho E_1^{\\dagger} @@ -1251,6 +1248,7 @@ def _to_openqasm( def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -1282,8 +1280,7 @@ def phase_damping(target: QubitSetInput, gamma: float) -> Iterable[Instruction]: ] def bind_values(self, **kwargs) -> Noise: - """ - Takes in parameters and attempts to assign them to values. + """Takes in parameters and attempts to assign them to values. Returns: Noise: A new Noise object of the same type with the requested @@ -1293,8 +1290,7 @@ def bind_values(self, **kwargs) -> Noise: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -1311,21 +1307,24 @@ def from_dict(cls, noise: dict) -> Noise: class Kraus(Noise): """User-defined noise channel that uses the provided matrices as Kraus operators This noise channel is shown as `NK` in circuit diagrams. - - Args: - matrices (Iterable[np.array]): A list of matrices that define a noise - channel. These matrices need to satisfy the requirement of CPTP map. - display_name (str): Name to be used for an instance of this general noise - channel for circuit diagrams. Defaults to `KR`. - - Raises: - ValueError: If any matrix in `matrices` is not a two-dimensional square - matrix, - or has a dimension length which is not a positive exponent of 2, - or the `matrices` do not satisfy CPTP condition. """ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): + """Inits `Kraus`. + + Args: + matrices (Iterable[ndarray]): A list of matrices that define a noise + channel. These matrices need to satisfy the requirement of CPTP map. + display_name (str): Name to be used for an instance of this general noise + channel for circuit diagrams. Defaults to `KR`. + + Raises: + ValueError: If any matrix in `matrices` is not a two-dimensional square + matrix, + or has a dimension length which is not a positive exponent of 2, + or the `matrices` do not satisfy CPTP condition. + + """ for matrix in matrices: verify_quantum_operator_matrix_dimensions(matrix) if not int(np.log2(matrix.shape[0])) == int(np.log2(matrices[0].shape[0])): @@ -1347,6 +1346,7 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): def to_matrix(self) -> Iterable[np.ndarray]: """Returns a matrix representation of this noise. + Returns: Iterable[ndarray]: A list of matrix representations of this noise. """ @@ -1354,7 +1354,7 @@ def to_matrix(self) -> Iterable[np.ndarray]: def _to_jaqcd(self, target: QubitSet) -> Any: return ir.Kraus.construct( - targets=[qubit for qubit in target], + targets=list(target), matrices=Kraus._transform_matrix_to_ir(self._matrices), ) @@ -1414,8 +1414,7 @@ def kraus( ) def to_dict(self) -> dict: - """ - Converts this object into a dictionary representation. Not implemented at this time. + """Converts this object into a dictionary representation. Not implemented at this time. Returns: dict: Not implemented at this time.. @@ -1424,8 +1423,7 @@ def to_dict(self) -> dict: @classmethod def from_dict(cls, noise: dict) -> Noise: - """ - Converts a dictionary representation of this class into this class. + """Converts a dictionary representation of this class into this class. Args: noise(dict): The dictionary representation of this noise. @@ -1442,8 +1440,7 @@ def from_dict(cls, noise: dict) -> Noise: def _ascii_representation( noise: str, parameters: list[Union[FreeParameterExpression, float]] ) -> str: - """ - Generates a formatted ascii representation of a noise. + """Generates a formatted ascii representation of a noise. Args: noise (str): The name of the noise. @@ -1455,7 +1452,7 @@ def _ascii_representation( param_list = [] for param in parameters: param_list.append( - str(param) if isinstance(param, FreeParameterExpression) else "{:.2g}".format(param) + str(param) if isinstance(param, FreeParameterExpression) else f"{param:.2g}" ) param_str = ",".join(param_list) return f"{noise}({param_str})" diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 03cd0714e..d3f3fc862 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -31,8 +31,7 @@ class Observable(QuantumOperator): - """ - Class `Observable` to represent a quantum observable. + """Class `Observable` to represent a quantum observable. Objects of this type can be used as input to `ResultType.Sample`, `ResultType.Variance`, `ResultType.Expectation` to specify the measurement basis. @@ -67,7 +66,7 @@ def to_ir( Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. + properties don't correspond to the `ir_type`. """ if ir_type == IRType.JAQCD: return self._to_jaqcd() @@ -94,8 +93,7 @@ def _to_openqasm( serialization_properties: OpenQASMSerializationProperties, target: QubitSet | None = None, ) -> str: - """ - Returns the openqasm string representation of the result type. + """Returns the openqasm string representation of the result type. Args: serialization_properties (OpenQASMSerializationProperties): The serialization properties @@ -109,7 +107,8 @@ def _to_openqasm( @property def coefficient(self) -> int: - """ + """The coefficient of the observable. + Returns: int: coefficient value of the observable. """ @@ -118,6 +117,7 @@ def coefficient(self) -> int: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: """Returns the basis rotation gates for this observable. + Returns: tuple[Gate, ...]: The basis rotation gates for this observable. """ @@ -126,8 +126,9 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. + Returns: - ndarray: The eigenvalues of this observable. + np.ndarray: The eigenvalues of this observable. """ raise NotImplementedError @@ -154,13 +155,13 @@ def register_observable(cls, observable: Observable) -> None: """ setattr(cls, observable.__name__, observable) - def __matmul__(self, other) -> Observable.TensorProduct: + def __matmul__(self, other: Observable) -> Observable.TensorProduct: if isinstance(other, Observable): return Observable.TensorProduct([self, other]) raise ValueError("Can only perform tensor products between observables.") - def __mul__(self, other) -> Observable: + def __mul__(self, other: Observable) -> Observable: """Scalar multiplication""" if isinstance(other, numbers.Number): observable_copy = deepcopy(self) @@ -168,16 +169,16 @@ def __mul__(self, other) -> Observable: return observable_copy raise TypeError("Observable coefficients must be numbers.") - def __rmul__(self, other) -> Observable: + def __rmul__(self, other: Observable) -> Observable: return self * other - def __add__(self, other): + def __add__(self, other: Observable): if not isinstance(other, Observable): raise ValueError("Can only perform addition between observables.") return Observable.Sum([self, other]) - def __sub__(self, other): + def __sub__(self, other: Observable): if not isinstance(other, Observable): raise ValueError("Can only perform subtraction between observables.") @@ -186,15 +187,14 @@ def __sub__(self, other): def __repr__(self) -> str: return f"{self.name}('qubit_count': {self.qubit_count})" - def __eq__(self, other) -> bool: + def __eq__(self, other: Observable) -> bool: if isinstance(other, Observable): return self.name == other.name return NotImplemented class StandardObservable(Observable): - """ - Class `StandardObservable` to represent a Pauli-like quantum observable with + """Class `StandardObservable` to represent a Pauli-like quantum observable with eigenvalues of (+1, -1). """ diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index dc0ff0782..2d5ccdb68 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -18,7 +18,7 @@ import math import numbers from copy import deepcopy -from typing import Union +from typing import ClassVar, Union import numpy as np @@ -37,9 +37,8 @@ class H(StandardObservable): """Hadamard operation as an observable.""" def __init__(self): - """ - Examples: - >>> Observable.H() + """Examples: + >>> Observable.H() """ super().__init__(ascii_symbols=["H"]) @@ -68,19 +67,18 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Ry(-math.pi / 4)]) + return tuple([Gate.Ry(-math.pi / 4)]) # noqa: C409 Observable.register_observable(H) -class I(Observable): # noqa: E742, E261 +class I(Observable): # noqa: E742 """Identity operation as an observable.""" def __init__(self): - """ - Examples: - >>> Observable.I() + """Examples: + >>> Observable.I() """ super().__init__(qubit_count=1, ascii_symbols=["I"]) @@ -112,6 +110,7 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. + Returns: np.ndarray: The eigenvalues of this observable. """ @@ -128,9 +127,8 @@ class X(StandardObservable): """Pauli-X operation as an observable.""" def __init__(self): - """ - Examples: - >>> Observable.X() + """Examples: + >>> Observable.X() """ super().__init__(ascii_symbols=["X"]) @@ -157,7 +155,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.H()]) + return tuple([Gate.H()]) # noqa: C409 Observable.register_observable(X) @@ -167,9 +165,8 @@ class Y(StandardObservable): """Pauli-Y operation as an observable.""" def __init__(self): - """ - Examples: - >>> Observable.Y() + """Examples: + >>> Observable.Y() """ super().__init__(ascii_symbols=["Y"]) @@ -196,7 +193,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Z(), Gate.S(), Gate.H()]) + return tuple([Gate.Z(), Gate.S(), Gate.H()]) # noqa: C409 Observable.register_observable(Y) @@ -206,9 +203,8 @@ class Z(StandardObservable): """Pauli-Z operation as an observable.""" def __init__(self): - """ - Examples: - >>> Observable.Z() + """Examples: + >>> Observable.Z() """ super().__init__(ascii_symbols=["Z"]) @@ -245,7 +241,8 @@ class TensorProduct(Observable): """Tensor product of observables""" def __init__(self, observables: list[Observable]): - """ + """Initializes a `TensorProduct`. + Args: observables (list[Observable]): List of observables for tensor product @@ -348,6 +345,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: """Returns the basis rotation gates for this observable. + Returns: tuple[Gate, ...]: The basis rotation gates for this observable. """ @@ -359,6 +357,7 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. + Returns: np.ndarray: The eigenvalues of this observable. """ @@ -400,7 +399,7 @@ def eigenvalue(self, index: int) -> float: def __repr__(self): return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" - def __eq__(self, other): + def __eq__(self, other: TensorProduct): return self.matrix_equivalence(other) @staticmethod @@ -432,7 +431,8 @@ class Sum(Observable): """Sum of observables""" def __init__(self, observables: list[Observable], display_name: str = "Hamiltonian"): - """ + """Inits a `Sum`. + Args: observables (list[Observable]): List of observables for Sum display_name (str): Name to use for an instance of this Sum @@ -456,11 +456,11 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni qubit_count = max(flattened_observables, key=lambda obs: obs.qubit_count).qubit_count super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) - def __mul__(self, other) -> Observable: + def __mul__(self, other: numbers.Number) -> Observable: """Scalar multiplication""" if isinstance(other, numbers.Number): sum_copy = deepcopy(self) - for i, obs in enumerate(sum_copy.summands): + for i, _obs in enumerate(sum_copy.summands): sum_copy._summands[i]._coef *= other return sum_copy raise TypeError("Observable coefficients must be numbers.") @@ -514,7 +514,7 @@ def eigenvalue(self, index: int) -> float: def __repr__(self): return "Sum(" + ", ".join([repr(o) for o in self.summands]) + ")" - def __eq__(self, other): + def __eq__(self, other: Sum): return repr(self) == repr(other) @staticmethod @@ -529,12 +529,13 @@ class Hermitian(Observable): """Hermitian matrix as an observable.""" # Cache of eigenpairs - _eigenpairs = {} + _eigenpairs: ClassVar = {} def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): - """ + """Inits a `Hermitian`. + Args: - matrix (numpy.ndarray): Hermitian matrix that defines the observable. + matrix (np.ndarray): Hermitian matrix that defines the observable. display_name (str): Name to use for an instance of this Hermitian matrix observable for circuit diagrams. Defaults to `Hermitian`. @@ -594,7 +595,7 @@ def _serialized_matrix_openqasm_matrix(self) -> str: def to_matrix(self) -> np.ndarray: return self.coefficient * self._matrix - def __eq__(self, other) -> bool: + def __eq__(self, other: Hermitian) -> bool: return self.matrix_equivalence(other) @property @@ -604,6 +605,7 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. + Returns: np.ndarray: The eigenvalues of this observable. """ @@ -614,8 +616,7 @@ def eigenvalue(self, index: int) -> float: @staticmethod def _get_eigendecomposition(matrix: np.ndarray) -> dict[str, np.ndarray]: - """ - Decomposes the Hermitian matrix into its eigenvectors and associated eigenvalues. + """Decomposes the Hermitian matrix into its eigenvectors and associated eigenvalues. The eigendecomposition is cached so that if another Hermitian observable is created with the same matrix, the eigendecomposition doesn't have to be recalculated. @@ -649,8 +650,7 @@ def __repr__(self): def observable_from_ir(ir_observable: list[Union[str, list[list[list[float]]]]]) -> Observable: - """ - Create an observable from the IR observable list. This can be a tensor product of + """Create an observable from the IR observable list. This can be a tensor product of observables or a single observable. Args: diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index 06e72c8a8..ccd63ac37 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -22,14 +22,14 @@ class Operator(ABC): @abstractmethod def name(self) -> str: """The name of the operator. + Returns: str: The name of the operator. """ @abstractmethod def to_ir(self, *args, **kwargs) -> Any: - """ - Converts the operator into the canonical intermediate representation. + """Converts the operator into the canonical intermediate representation. If the operator is passed in a request, this method is called before it is passed. Returns: diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index df67bf199..068c4171d 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -25,7 +25,8 @@ class QuantumOperator(Operator): """A quantum operator is the definition of a quantum operation for a quantum device.""" def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): - """ + """Initializes a `QuantumOperator`. + Args: qubit_count (Optional[int]): Number of qubits this quantum operator acts on. If all instances of the operator act on the same number of qubits, this argument @@ -48,7 +49,6 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): ``fixed_qubit_count`` is implemented and and not equal to ``qubit_count``, or ``len(ascii_symbols) != qubit_count`` """ - fixed_qubit_count = self.fixed_qubit_count() if fixed_qubit_count is NotImplemented: self._qubit_count = qubit_count @@ -79,8 +79,7 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): @staticmethod def fixed_qubit_count() -> int: - """ - Returns the number of qubits this quantum operator acts on, + """Returns the number of qubits this quantum operator acts on, if instances are guaranteed to act on the same number of qubits. If different instances can act on a different number of qubits, @@ -103,33 +102,45 @@ def ascii_symbols(self) -> tuple[str, ...]: @property def name(self) -> str: - """ - Returns the name of the quantum operator + """Returns the name of the quantum operator Returns: str: The name of the quantum operator as a string """ return self.__class__.__name__ - def to_ir(self, *args, **kwargs) -> Any: + def to_ir(self, *args: Any, **kwargs: Any) -> Any: """Returns IR representation of quantum operator. + Args: + *args (Any): Not Implemented. + **kwargs (Any): Not Implemented. + + Raises: + NotImplementError: Not Implemented. + Returns: Any: The the canonical intermediate representation of the operator. """ raise NotImplementedError("to_ir has not been implemented yet.") - def to_matrix(self, *args, **kwargs) -> np.ndarray: - """Returns a matrix representation of the quantum operator + def to_matrix(self, *args: Any, **kwargs: Any) -> np.ndarray: + """Returns a matrix representation of the quantum operator. + + Args: + *args (Any): Not Implemented. + **kwargs (Any): Not Implemented. + + Raises: + NotImplementError: Not Implemented. Returns: - ndarray: A matrix representation of the quantum operator + np.ndarray: A matrix representation of the quantum operator """ raise NotImplementedError("to_matrix has not been implemented yet.") def matrix_equivalence(self, other: QuantumOperator) -> bool: - """ - Whether the matrix form of two quantum operators are equivalent + """Whether the matrix form of two quantum operators are equivalent Args: other (QuantumOperator): Quantum operator instance to compare this quantum operator to diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index a264d0b38..8d64888ed 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -18,8 +18,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.ndarray) -> None: - """ - Verifies matrix is square and matrix dimensions are positive powers of 2, + """Verifies matrix is square and matrix dimensions are positive powers of 2, raising `ValueError` otherwise. Args: @@ -40,8 +39,7 @@ def verify_quantum_operator_matrix_dimensions(matrix: np.ndarray) -> None: def is_hermitian(matrix: np.ndarray) -> bool: - r""" - Whether matrix is Hermitian + r"""Whether matrix is Hermitian A square matrix :math:`U` is Hermitian if @@ -59,11 +57,10 @@ def is_hermitian(matrix: np.ndarray) -> bool: def is_square_matrix(matrix: np.ndarray) -> bool: - """ - Whether matrix is square, meaning it has exactly two dimensions and the dimensions are equal + """Whether matrix is square, meaning it has exactly two dimensions and the dimensions are equal Args: - matrix (ndarray): matrix to verify + matrix (np.ndarray): matrix to verify Returns: bool: If matrix is square @@ -72,8 +69,7 @@ def is_square_matrix(matrix: np.ndarray) -> bool: def is_unitary(matrix: np.ndarray) -> bool: - r""" - Whether matrix is unitary + r"""Whether matrix is unitary A square matrix :math:`U` is unitary if @@ -83,7 +79,7 @@ def is_unitary(matrix: np.ndarray) -> bool: and :math:`I` is the identity matrix. Args: - matrix (ndarray): matrix to verify + matrix (np.ndarray): matrix to verify Returns: bool: If matrix is unitary @@ -92,8 +88,7 @@ def is_unitary(matrix: np.ndarray) -> bool: def is_cptp(matrices: Iterable[np.ndarray]) -> bool: - """ - Whether a transformation defined by these matrics as Kraus operators is a + """Whether a transformation defined by these matrics as Kraus operators is a completely positive trace preserving (CPTP) map. This is the requirement for a transformation to be a quantum channel. Reference: Section 8.2.3 in Nielsen & Chuang (2010) 10th edition. @@ -108,17 +103,16 @@ def is_cptp(matrices: Iterable[np.ndarray]) -> bool: return np.allclose(E, np.eye(*E.shape)) -@lru_cache() +@lru_cache def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: - """ - Get the eigenvalues of Pauli operators and their tensor products as + """Get the eigenvalues of Pauli operators and their tensor products as an immutable Numpy ndarray. Args: num_qubits (int): the number of qubits the operator acts on Returns: - ndarray: the eigenvalues of a Pauli product operator of the given size + np.ndarray: the eigenvalues of a Pauli product operator of the given size """ if num_qubits == 1: eigs = np.array([1, -1]) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index c6877262e..b66d4da67 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -28,14 +28,14 @@ class ResultType: - """ - Class `ResultType` represents a requested result type for the circuit. + """Class `ResultType` represents a requested result type for the circuit. This class is considered the result type definition containing the metadata that defines what a requested result type is and what it does. """ def __init__(self, ascii_symbols: list[str]): - """ + """Initializes a `ResultType`. + Args: ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when printing a diagram of circuits. @@ -43,7 +43,6 @@ def __init__(self, ascii_symbols: list[str]): Raises: ValueError: `ascii_symbols` is `None` """ - if ascii_symbols is None: raise ValueError("ascii_symbols must not be None") @@ -56,8 +55,7 @@ def ascii_symbols(self) -> list[str]: @property def name(self) -> str: - """ - Returns the name of the result type + """Returns the name of the result type Returns: str: The name of the result type as a string @@ -73,7 +71,7 @@ def to_ir( """Returns IR object of the result type Args: - ir_type(IRType) : The IRType to use for converting the result type object to its + ir_type(IRType): The IRType to use for converting the result type object to its IR representation. Defaults to IRType.JAQCD. serialization_properties (SerializationProperties | None): The serialization properties to use while serializing the object to the IR representation. The serialization @@ -84,7 +82,7 @@ def to_ir( Raises: ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization - properties don't correspond to the `ir_type`. + properties don't correspond to the `ir_type`. """ if ir_type == IRType.JAQCD: return self._to_jaqcd() @@ -105,13 +103,15 @@ def _to_jaqcd(self) -> Any: raise NotImplementedError("to_jaqcd has not been implemented yet.") def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: - """ - Returns the openqasm string representation of the result type. + """Returns the openqasm string representation of the result type. Args: serialization_properties (OpenQASMSerializationProperties): The serialization properties to use while serializing the object to the IR representation. + Raises: + NotImplementedError: not implemented. + Returns: str: Representing the openqasm representation of the result type. """ @@ -122,8 +122,7 @@ def copy( target_mapping: dict[QubitInput, QubitInput] | None = None, target: QubitSetInput | None = None, ) -> ResultType: - """ - Return a shallow copy of the result type. + """Return a shallow copy of the result type. Note: If `target_mapping` is specified, then `self.target` is mapped to the specified @@ -180,8 +179,7 @@ def __hash__(self) -> int: class ObservableResultType(ResultType): - """ - Result types with observables and targets. + """Result types with observables and targets. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. @@ -192,7 +190,8 @@ class ObservableResultType(ResultType): def __init__( self, ascii_symbols: list[str], observable: Observable, target: QubitSetInput | None = None ): - """ + """Initializes an `ObservableResultType`. + Args: ascii_symbols (list[str]): ASCII string symbols for the result type. This is used when printing a diagram of circuits. @@ -215,29 +214,28 @@ def __init__( raise ValueError( f"Observable {self._observable} must only operate on 1 qubit for target=None" ) - else: - if isinstance(observable, Sum): # nested target - if len(target) != len(observable.summands): + elif isinstance(observable, Sum): # nested target + if len(target) != len(observable.summands): + raise ValueError( + "Sum observable's target shape must be a nested list where each term's " + "target length is equal to the observable term's qubits count." + ) + self._target = [QubitSet(term_target) for term_target in target] + for term_target, obs in zip(target, observable.summands): + if obs.qubit_count != len(term_target): raise ValueError( "Sum observable's target shape must be a nested list where each term's " "target length is equal to the observable term's qubits count." ) - self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(target, observable.summands): - if obs.qubit_count != len(term_target): - raise ValueError( - "Sum observable's target shape must be a nested list where each term's " - "target length is equal to the observable term's qubits count." - ) - elif self._observable.qubit_count != len(self._target): - raise ValueError( - f"Observable's qubit count {self._observable.qubit_count} and " - f"the size of the target qubit set {self._target} must be equal" - ) - elif self._observable.qubit_count != len(self.ascii_symbols): - raise ValueError( - "Observable's qubit count and the number of ASCII symbols must be equal" - ) + elif self._observable.qubit_count != len(self._target): + raise ValueError( + f"Observable's qubit count {self._observable.qubit_count} and " + f"the size of the target qubit set {self._target} must be equal" + ) + elif self._observable.qubit_count != len(self.ascii_symbols): + raise ValueError( + "Observable's qubit count and the number of ASCII symbols must be equal" + ) @property def observable(self) -> Observable: @@ -250,12 +248,13 @@ def target(self) -> QubitSet: @target.setter def target(self, target: QubitSetInput) -> None: """Sets the target. + Args: target (QubitSetInput): The new target. """ self._target = QubitSet(target) - def __eq__(self, other) -> bool: + def __eq__(self, other: ObservableResultType) -> bool: if isinstance(other, ObservableResultType): return ( self.name == other.name @@ -275,8 +274,7 @@ def __hash__(self) -> int: class ObservableParameterResultType(ObservableResultType): - """ - Result types with observables, targets and parameters. + """Result types with observables, targets and parameters. If no targets are specified, the observable must only operate on 1 qubit and it will be applied to all qubits in parallel. Otherwise, the number of specified targets must be equivalent to the number of qubits the observable can be applied to. diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 0a1a1c630..0b73c5e7b 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -41,8 +41,7 @@ class StateVector(ResultType): - """ - The full state vector as a requested result type. + """The full state vector as a requested result type. This is available on simulators only when `shots=0`. """ @@ -68,7 +67,7 @@ def state_vector() -> ResultType: """ return ResultType.StateVector() - def __eq__(self, other) -> bool: + def __eq__(self, other: StateVector) -> bool: if isinstance(other, StateVector): return True return False @@ -86,13 +85,13 @@ def __hash__(self) -> int: class DensityMatrix(ResultType): - """ - The full density matrix as a requested result type. + """The full density matrix as a requested result type. This is available on simulators only when `shots=0`. """ def __init__(self, target: QubitSetInput | None = None): - """ + """Inits a `DensityMatrix`. + Args: target (QubitSetInput | None): The target qubits of the reduced density matrix. Default is `None`, and the @@ -112,6 +111,7 @@ def target(self) -> QubitSet: @target.setter def target(self, target: QubitSetInput) -> None: """Sets the target qubit set. + Args: target (QubitSetInput): The target qubit set. """ @@ -136,6 +136,7 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties @circuit.subroutine(register=True) def density_matrix(target: QubitSetInput | None = None) -> ResultType: """Registers this function into the circuit class. + Args: target (QubitSetInput | None): The target qubits of the reduced density matrix. Default is `None`, and the @@ -149,7 +150,7 @@ def density_matrix(target: QubitSetInput | None = None) -> ResultType: """ return ResultType.DensityMatrix(target=target) - def __eq__(self, other) -> bool: + def __eq__(self, other: DensityMatrix) -> bool: if isinstance(other, DensityMatrix): return self.target == other.target return False @@ -170,8 +171,7 @@ def __hash__(self) -> int: class AdjointGradient(ObservableParameterResultType): - """ - The gradient of the expectation value of the provided observable, applied to target, + """The gradient of the expectation value of the provided observable, applied to target, with respect to the given parameter. """ @@ -181,7 +181,8 @@ def __init__( target: list[QubitSetInput] | None = None, parameters: list[Union[str, FreeParameter]] | None = None, ): - """ + """Inits an `AdjointGradient`. + Args: observable (Observable): The expectation value of this observable is the function against which parameters in the gradient are differentiated. @@ -197,6 +198,7 @@ def __init__( ValueError: If the observable's qubit count does not equal the number of target qubits, or if `target=None` and the observable's qubit count is not 1. + Examples: >>> ResultType.AdjointGradient(observable=Observable.Z(), target=0, parameters=["alpha", "beta"]) @@ -209,7 +211,6 @@ def __init__( >>> parameters=["alpha", "beta"], >>> ) """ - if isinstance(observable, Sum): target_qubits = reduce(QubitSet.union, map(QubitSet, target), QubitSet()) else: @@ -274,13 +275,13 @@ def adjoint_gradient( class Amplitude(ResultType): - """ - The amplitude of the specified quantum states as a requested result type. + """The amplitude of the specified quantum states as a requested result type. This is available on simulators only when `shots=0`. """ def __init__(self, state: list[str]): - """ + """Initializes an `Amplitude`. + Args: state (list[str]): list of quantum states as strings with "0" and "1" @@ -332,7 +333,7 @@ def amplitude(state: list[str]) -> ResultType: """ return ResultType.Amplitude(state=state) - def __eq__(self, other): + def __eq__(self, other: Amplitude): if isinstance(other, Amplitude): return self.state == other.state return False @@ -362,7 +363,8 @@ class Probability(ResultType): """ def __init__(self, target: QubitSetInput | None = None): - """ + """Inits a `Probability`. + Args: target (QubitSetInput | None): The target qubits that the result type is requested for. Default is `None`, which means all qubits for the @@ -382,6 +384,7 @@ def target(self) -> QubitSet: @target.setter def target(self, target: QubitSetInput) -> None: """Sets the target qubit set. + Args: target (QubitSetInput): The target qubit set. """ @@ -420,7 +423,7 @@ def probability(target: QubitSetInput | None = None) -> ResultType: """ return ResultType.Probability(target=target) - def __eq__(self, other) -> bool: + def __eq__(self, other: Probability) -> bool: if isinstance(other, Probability): return self.target == other.target return False @@ -452,16 +455,14 @@ class Expectation(ObservableResultType): """ def __init__(self, observable: Observable, target: QubitSetInput | None = None): - """ + """Inits an `Expectation`. + Args: observable (Observable): the observable for the result type target (QubitSetInput | None): Target qubits that the result type is requested for. Default is `None`, which means the observable must operate only on 1 qubit and it is applied to all qubits in parallel. - Raises: - ValueError: If the observable's qubit count does not equal the number of target - qubits, or if `target=None` and the observable's qubit count is not 1. Examples: >>> ResultType.Expectation(observable=Observable.Z(), target=0) @@ -527,17 +528,14 @@ class Sample(ObservableResultType): """ def __init__(self, observable: Observable, target: QubitSetInput | None = None): - """ + """Inits a `Sample`. + Args: observable (Observable): the observable for the result type target (QubitSetInput | None): Target qubits that the result type is requested for. Default is `None`, which means the observable must operate only on 1 qubit and it is applied to all qubits in parallel. - Raises: - ValueError: If the observable's qubit count is not equal to the number of target - qubits, or if `target=None` and the observable's qubit count is not 1. - Examples: >>> ResultType.Sample(observable=Observable.Z(), target=0) @@ -603,7 +601,8 @@ class Variance(ObservableResultType): """ def __init__(self, observable: Observable, target: QubitSetInput | None = None): - """ + """Inits a `Variance`. + Args: observable (Observable): the observable for the result type target (QubitSetInput | None): Target qubits that the diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index 1e0826e80..afcb5d118 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -23,8 +23,7 @@ class IRType(str, Enum): class QubitReferenceType(str, Enum): - """ - Defines how qubits should be referenced in the generated OpenQASM string. + """Defines how qubits should be referenced in the generated OpenQASM string. See https://qiskit.github.io/openqasm/language/types.html#quantum-types for details. """ @@ -35,8 +34,7 @@ class QubitReferenceType(str, Enum): @dataclass class OpenQASMSerializationProperties: - """ - Properties for serializing a circuit to OpenQASM. + """Properties for serializing a circuit to OpenQASM. qubit_reference_type (QubitReferenceType): determines whether to use logical qubits or physical qubits (q[i] vs $i). @@ -46,6 +44,7 @@ class OpenQASMSerializationProperties: def format_target(self, target: int) -> str: """Format a target qubit to the appropriate OpenQASM representation. + Args: target (int): The target qubit. diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 74dae1bbd..9537460ef 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -15,10 +15,9 @@ from typing import Union import braket.circuits.gates as braket_gates -import braket.circuits.noises as noises -import braket.circuits.result_types as ResultTypes +import braket.circuits.result_types as ResultTypes # noqa: N812 import braket.ir.jaqcd.shared_models as models -from braket.circuits import Observable, observables +from braket.circuits import Observable, noises, observables from braket.ir.jaqcd import ( Amplitude, DensityMatrix, @@ -99,6 +98,14 @@ def get_observable(obs: Union[models.Observable, list]) -> Observable: + """Gets the observable. + + Args: + obs (Union[Observable, list]): The observable(s) to get translated. + + Returns: + Observable: The translated observable. + """ return _get_observable(obs) @@ -140,39 +147,39 @@ def braket_result_to_result_type(result: Results) -> None: @_braket_result_to_result_type.register(Amplitude) -def _(result): +def _(result: Results) -> Amplitude: return ResultTypes.Amplitude(state=result.states) @_braket_result_to_result_type.register(Expectation) -def _(result): +def _(result: Results) -> Expectation: tensor_product = get_tensor_product(result.observable) return ResultTypes.Expectation(observable=tensor_product, target=result.targets) @_braket_result_to_result_type.register(Probability) -def _(result): +def _(result: Results) -> Probability: return ResultTypes.Probability(result.targets) @_braket_result_to_result_type.register(Sample) -def _(result): +def _(result: Results) -> Sample: tensor_product = get_tensor_product(result.observable) return ResultTypes.Sample(observable=tensor_product, target=result.targets) @_braket_result_to_result_type.register(StateVector) -def _(result): +def _(result: Results) -> StateVector: return ResultTypes.StateVector() @_braket_result_to_result_type.register(DensityMatrix) -def _(result): +def _(result: Results): return ResultTypes.DensityMatrix(target=result.targets) @_braket_result_to_result_type.register(Variance) -def _(result): +def _(result: Results): tensor_product = get_tensor_product(result.observable) return ResultTypes.Variance(observable=tensor_product, target=result.targets) diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index ebc3c7878..9fa404284 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -26,8 +26,7 @@ def calculate_unitary_big_endian( instructions: Iterable[Instruction], qubits: QubitSet ) -> np.ndarray: - """ - Returns the unitary matrix representation for all the `instructions` on qubits `qubits`. + """Returns the unitary matrix representation for all the `instruction`s on qubits `qubits`. Note: The performance of this method degrades with qubit count. It might be slow for diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index d61f90162..3f2a28e41 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -13,7 +13,7 @@ import warnings from abc import ABC, abstractmethod -from typing import Optional, Union +from typing import Any, Optional, Union from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem @@ -30,7 +30,8 @@ class Device(ABC): """An abstraction over quantum devices that includes quantum computers and simulators.""" def __init__(self, name: str, status: str): - """ + """Initializes a `Device`. + Args: name (str): Name of quantum device status (str): Status of quantum device @@ -58,6 +59,8 @@ def run( inputs (Optional[dict[str, float]]): Inputs to be passed along with the IR. If IR is an OpenQASM Program, the inputs will be updated with this value. Not all devices and IR formats support inputs. Default: {}. + *args (Any): Arbitrary arguments. + **kwargs (Any): Arbitrary keyword arguments. Returns: QuantumTask: The QuantumTask tracking task execution on this device @@ -73,8 +76,8 @@ def run_batch( shots: Optional[int], max_parallel: Optional[int], inputs: Optional[Union[dict[str, float], list[dict[str, float]]]], - *args, - **kwargs, + *args: Any, + **kwargs: Any, ) -> QuantumTaskBatch: """Executes a batch of quantum tasks in parallel @@ -88,6 +91,8 @@ def run_batch( inputs (Optional[Union[dict[str, float], list[dict[str, float]]]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. + *args (Any): Arbitrary arguments. + **kwargs (Any): Arbitrary keyword arguments. Returns: QuantumTaskBatch: A batch containing all of the qauntum tasks run diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 3140e8534..faee13fdf 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -17,7 +17,7 @@ from itertools import repeat from multiprocessing import Pool from os import cpu_count -from typing import Optional, Union +from typing import Any, Optional, Union import pkg_resources @@ -56,7 +56,8 @@ def __init__( backend: Union[str, BraketSimulator] = "default", noise_model: Optional[NoiseModel] = None, ): - """ + """Initializes a `LocalSimulator`. + Args: backend (Union[str, BraketSimulator]): The name of the simulator backend or the actual simulator instance to use for simulation. Defaults to the @@ -80,8 +81,8 @@ def run( task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], shots: int = 0, inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, + *args: Any, + **kwargs: Any, ) -> LocalQuantumTask: """Runs the given task with the wrapped local simulator. @@ -95,6 +96,8 @@ def run( inputs (Optional[dict[str, float]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. + *args (Any): Arbitrary arguments. + **kwargs(Any): Arbitrary keyword arguments. Returns: LocalQuantumTask: A LocalQuantumTask object containing the results @@ -129,7 +132,7 @@ def run_batch( # noqa: C901 """Executes a batch of quantum tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): # noqa + task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): Single instance or list of quantum task specification. shots (Optional[int]): The number of times to run the quantum task. Default: 0. @@ -144,7 +147,7 @@ def run_batch( # noqa: C901 See Also: `braket.tasks.local_quantum_task_batch.LocalQuantumTaskBatch` - """ + """ # noqa E501 inputs = inputs or {} if self._noise_model: @@ -165,9 +168,7 @@ def run_batch( # noqa: C901 if not single_task and not single_input: if len(task_specifications) != len(inputs): - raise ValueError( - "Multiple inputs and task specifications must " "be equal in number." - ) + raise ValueError("Multiple inputs and task specifications must be equal in number.") if single_task: task_specifications = repeat(task_specifications) @@ -203,7 +204,8 @@ def properties(self) -> DeviceCapabilities: Please see `braket.device_schema` in amazon-braket-schemas-python_ - .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python""" + .. _amazon-braket-schemas-python: https://github.com/aws/amazon-braket-schemas-python + """ return self._delegate.properties @staticmethod diff --git a/src/braket/error_mitigation/debias.py b/src/braket/error_mitigation/debias.py index 305bf7b78..8beddc7ef 100644 --- a/src/braket/error_mitigation/debias.py +++ b/src/braket/error_mitigation/debias.py @@ -16,9 +16,7 @@ class Debias(ErrorMitigation): - """ - The debias error mitigation scheme. This scheme takes no parameters. - """ + """The debias error mitigation scheme. This scheme takes no parameters.""" def serialize(self) -> list[error_mitigation.Debias]: return [error_mitigation.Debias()] diff --git a/src/braket/error_mitigation/error_mitigation.py b/src/braket/error_mitigation/error_mitigation.py index 79b1f3e30..95e6b6582 100644 --- a/src/braket/error_mitigation/error_mitigation.py +++ b/src/braket/error_mitigation/error_mitigation.py @@ -16,9 +16,14 @@ class ErrorMitigation: def serialize(self) -> list[error_mitigation.ErrorMitigationScheme]: - """ + """This returns a list of service-readable error mitigation + scheme descriptions. + Returns: list[ErrorMitigationScheme]: A list of service-readable error mitigation scheme descriptions. + + Raises: + NotImplementedError: Not implemented in the base class. """ raise NotImplementedError("serialize is not implemented.") diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index 20100d944..c443d1b44 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -15,8 +15,7 @@ def running_in_jupyter() -> bool: - """ - Determine if running within Jupyter. + """Determine if running within Jupyter. Inspired by https://github.com/ipython/ipython/issues/11694 diff --git a/src/braket/jobs/config.py b/src/braket/jobs/config.py index a598388e4..7c84b42dd 100644 --- a/src/braket/jobs/config.py +++ b/src/braket/jobs/config.py @@ -54,8 +54,8 @@ class DeviceConfig: class S3DataSourceConfig: - """ - Data source for data that lives on S3 + """Data source for data that lives on S3. + Attributes: config (dict[str, dict]): config passed to the Braket API """ diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index f2ec9b6fa..0386ed7a7 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -26,9 +26,8 @@ def save_job_checkpoint( checkpoint_file_suffix: str = "", data_format: PersistedJobDataFormat = PersistedJobDataFormat.PLAINTEXT, ) -> None: - """ - Saves the specified `checkpoint_data` to the local output directory, specified by the container - environment variable `CHECKPOINT_DIR`, with the filename + """Saves the specified `checkpoint_data` to the local output directory, specified by + the container environment variable `CHECKPOINT_DIR`, with the filename `f"{job_name}(_{checkpoint_file_suffix}).json"`. The `job_name` refers to the name of the current job and is retrieved from the container environment variable `JOB_NAME`. The `checkpoint_data` values are serialized to the specified `data_format`. @@ -68,8 +67,7 @@ def save_job_checkpoint( def load_job_checkpoint( job_name: str | None = None, checkpoint_file_suffix: str = "" ) -> dict[str, Any]: - """ - Loads the job checkpoint data stored for the job named 'job_name', with the checkpoint + """Loads the job checkpoint data stored for the job named 'job_name', with the checkpoint file that ends with the `checkpoint_file_suffix`. The `job_name` can refer to any job whose checkpoint data you expect to be available in the file path specified by the `CHECKPOINT_DIR` container environment variable. If not provided, this function will use the currently running @@ -104,7 +102,7 @@ def load_job_checkpoint( if checkpoint_file_suffix else f"{checkpoint_directory}/{job_name}.json" ) - with open(checkpoint_file_path, "r") as f: + with open(checkpoint_file_path) as f: persisted_data = PersistedJobData.parse_raw(f.read()) deserialized_data = deserialize_values( persisted_data.dataDictionary, persisted_data.dataFormat @@ -115,7 +113,7 @@ def load_job_checkpoint( def _load_persisted_data(filename: str | Path | None = None) -> PersistedJobData: filename = filename or Path(get_results_dir()) / "results.json" try: - with open(filename, mode="r") as f: + with open(filename) as f: return PersistedJobData.parse_raw(f.read()) except FileNotFoundError: return PersistedJobData( @@ -125,8 +123,7 @@ def _load_persisted_data(filename: str | Path | None = None) -> PersistedJobData def load_job_result(filename: str | Path | None = None) -> dict[str, Any]: - """ - Loads job result of currently running job. + """Loads job result of currently running job. Args: filename (str | Path | None): Location of job results. Default `results.json` in job @@ -145,8 +142,7 @@ def save_job_result( result_data: dict[str, Any] | Any, data_format: PersistedJobDataFormat | None = None, ) -> None: - """ - Saves the `result_data` to the local output directory that is specified by the container + """Saves the `result_data` to the local output directory that is specified by the container environment variable `AMZN_BRAKET_JOB_RESULTS_DIR`, with the filename 'results.json'. The `result_data` values are serialized to the specified `data_format`. @@ -160,6 +156,9 @@ def save_job_result( data_format (PersistedJobDataFormat | None): The data format used to serialize the values. Note that for `PICKLED` data formats, the values are base64 encoded after serialization. Default: PersistedJobDataFormat.PLAINTEXT. + + Raises: + TypeError: Unsupported data format. """ if not isinstance(result_data, dict): result_data = {"result": result_data} diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py index 4fba9315c..ad42006de 100644 --- a/src/braket/jobs/environment_variables.py +++ b/src/braket/jobs/environment_variables.py @@ -16,8 +16,7 @@ def get_job_name() -> str: - """ - Get the name of the current job. + """Get the name of the current job. Returns: str: The name of the job if in a job, else an empty string. @@ -26,8 +25,7 @@ def get_job_name() -> str: def get_job_device_arn() -> str: - """ - Get the device ARN of the current job. If not in a job, default to "local:none/none". + """Get the device ARN of the current job. If not in a job, default to "local:none/none". Returns: str: The device ARN of the current job or "local:none/none". @@ -36,8 +34,7 @@ def get_job_device_arn() -> str: def get_input_data_dir(channel: str = "input") -> str: - """ - Get the job input data directory. + """Get the job input data directory. Args: channel (str): The name of the input channel. Default value @@ -53,8 +50,7 @@ def get_input_data_dir(channel: str = "input") -> str: def get_results_dir() -> str: - """ - Get the job result directory. + """Get the job result directory. Returns: str: The results directory, defaulting to current working directory. @@ -63,8 +59,7 @@ def get_results_dir() -> str: def get_checkpoint_dir() -> str: - """ - Get the job checkpoint directory. + """Get the job checkpoint directory. Returns: str: The checkpoint directory, defaulting to current working directory. @@ -73,13 +68,12 @@ def get_checkpoint_dir() -> str: def get_hyperparameters() -> dict[str, str]: - """ - Get the job hyperparameters as a dict, with the values stringified. + """Get the job hyperparameters as a dict, with the values stringified. Returns: dict[str, str]: The hyperparameters of the job. """ if "AMZN_BRAKET_HP_FILE" in os.environ: - with open(os.getenv("AMZN_BRAKET_HP_FILE"), "r") as f: + with open(os.getenv("AMZN_BRAKET_HP_FILE")) as f: return json.load(f) return {} diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index b8e1e58bf..3cd622e37 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -168,9 +168,13 @@ def hybrid_job( def _hybrid_job(entry_point: Callable) -> Callable: @functools.wraps(entry_point) - def job_wrapper(*args, **kwargs) -> Callable: - """ - The job wrapper. + def job_wrapper(*args: Any, **kwargs: Any) -> Callable: + """The job wrapper. + + Args: + *args (Any): Arbitrary arguments. + **kwargs (Any): Arbitrary keyword arguments. + Returns: Callable: the callable for creating a Hybrid Job. """ @@ -322,7 +326,8 @@ def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> di hyperparameters.update(**value) else: warnings.warn( - "Positional only arguments will not be logged to the hyperparameters file." + "Positional only arguments will not be logged to the hyperparameters file.", + stacklevel=1, ) return {name: _sanitize(value) for name, value in hyperparameters.items()} @@ -351,8 +356,7 @@ def _sanitize(hyperparameter: Any) -> str: def _process_input_data(input_data: dict) -> list[str]: - """ - Create symlinks to data + """Create symlinks to data. Logic chart for how the service moves files into the data directory on the instance: input data matches exactly one file: cwd/filename -> channel/filename diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index 3a3346abe..af6c5012a 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -15,7 +15,6 @@ import os from enum import Enum from functools import cache -from typing import Dict, Set class Framework(str, Enum): @@ -26,7 +25,15 @@ class Framework(str, Enum): PL_PYTORCH = "PL_PYTORCH" -def built_in_images(region: str) -> Set[str]: +def built_in_images(region: str) -> set[str]: + """Checks a region for built in Braket images. + + Args: + region (str): The AWS region to check for images + + Returns: + set[str]: returns a set of built images + """ return {retrieve_image(framework, region) for framework in Framework} @@ -53,25 +60,25 @@ def retrieve_image(framework: Framework, region: str) -> str: return f"{registry}.dkr.ecr.{region}.amazonaws.com/{tag}" -def _config_for_framework(framework: Framework) -> Dict[str, str]: +def _config_for_framework(framework: Framework) -> dict[str, str]: """Loads the JSON config for the given framework. Args: framework (Framework): The framework whose config needs to be loaded. Returns: - Dict[str, str]: Dict that contains the configuration for the specified framework. + dict[str, str]: Dict that contains the configuration for the specified framework. """ fname = os.path.join(os.path.dirname(__file__), "image_uri_config", f"{framework.lower()}.json") with open(fname) as f: return json.load(f) -def _registry_for_region(config: Dict[str, str], region: str) -> str: +def _registry_for_region(config: dict[str, str], region: str) -> str: """Retrieves the registry for the specified region from the configuration. Args: - config (Dict[str, str]): Dict containing the framework configuration. + config (dict[str, str]): Dict containing the framework configuration. region (str): str that specifies the region for which the registry is retrieved. Returns: diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index f516d9693..af6806157 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -117,6 +117,9 @@ def create( container image. Optional. Default: True. + Raises: + ValueError: Local directory with the job name already exists. + Returns: LocalQuantumJob: The representation of a local Braket Hybrid Job. """ @@ -166,11 +169,15 @@ def create( return LocalQuantumJob(f"local:job/{job_name}", run_log) def __init__(self, arn: str, run_log: str | None = None): - """ + """Initializes a `LocalQuantumJob`. + Args: arn (str): The ARN of the hybrid job. - run_log (str | None): The container output log of running the hybrid job with the - given arn. + run_log (str | None): The container output log of running the hybrid job with the given + arn. + + Raises: + ValueError: Local job is not found. """ if not arn.startswith("local:job/"): raise ValueError(f"Arn {arn} is not a valid local job arn") @@ -194,12 +201,15 @@ def name(self) -> str: def run_log(self) -> str: """Gets the run output log from running the hybrid job. + Raises: + ValueError: The log file is not found. + Returns: str: The container output log from running the hybrid job. """ if not self._run_log: try: - with open(os.path.join(self.name, "log.txt"), "r") as log_file: + with open(os.path.join(self.name, "log.txt")) as log_file: self._run_log = log_file.read() except FileNotFoundError: raise ValueError(f"Unable to find logs in the local job directory {self.name}.") @@ -207,11 +217,13 @@ def run_log(self) -> str: def state(self, use_cached_value: bool = False) -> str: """The state of the hybrid job. + Args: use_cached_value (bool): If `True`, uses the value most recently retrieved value from the Amazon Braket `GetJob` operation. If `False`, calls the `GetJob` operation to retrieve metadata, which also updates the cached value. Default = `False`. + Returns: str: Returns "COMPLETED". """ @@ -219,11 +231,13 @@ def state(self, use_cached_value: bool = False) -> str: def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: """When running the hybrid job in local mode, the metadata is not available. + Args: use_cached_value (bool): If `True`, uses the value most recently retrieved from the Amazon Braket `GetJob` operation, if it exists; if does not exist, `GetJob` is called to retrieve the metadata. If `False`, always calls `GetJob`, which also updates the cached value. Default: `False`. + Returns: dict[str, Any]: None """ @@ -231,6 +245,7 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: def cancel(self) -> str: """When running the hybrid job in local mode, the cancelling a running is not possible. + Returns: str: None """ @@ -260,7 +275,7 @@ def result( poll_timeout_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = QuantumJob.DEFAULT_RESULTS_POLL_INTERVAL, ) -> dict[str, Any]: - """Retrieves the hybrid job result persisted using save_job_result() function. + """Retrieves the `LocalQuantumJob` result persisted using `save_job_result` function. Args: poll_timeout_seconds (float): The polling timeout, in seconds, for `result()`. @@ -268,11 +283,14 @@ def result( poll_interval_seconds (float): The polling interval, in seconds, for `result()`. Default: 5 seconds. + Raises: + ValueError: The local job directory does not exist. + Returns: dict[str, Any]: Dict specifying the hybrid job results. """ try: - with open(os.path.join(self.name, "results.json"), "r") as f: + with open(os.path.join(self.name, "results.json")) as f: persisted_data = PersistedJobData.parse_raw(f.read()) deserialized_data = deserialize_values( persisted_data.dataDictionary, persisted_data.dataFormat diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index ea5625623..c4432dcf7 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -17,12 +17,11 @@ import subprocess from logging import Logger, getLogger from pathlib import PurePosixPath -from typing import Dict, List from braket.aws.aws_session import AwsSession -class _LocalJobContainer(object): +class _LocalJobContainer: """Uses docker CLI to run Braket Hybrid Jobs on a local docker container.""" ECR_URI_PATTERN = r"^((\d+)\.dkr\.ecr\.([^.]+)\.[^/]*)/([^:]*):(.*)$" @@ -39,6 +38,7 @@ def __init__( container. The function "end_session" must be called when the container is no longer needed. + Args: image_uri (str): The URI of the container image to run. aws_session (AwsSession | None): AwsSession for connecting to AWS Services. @@ -65,16 +65,17 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._end_session() @staticmethod - def _envs_to_list(environment_variables: Dict[str, str]) -> List[str]: + def _envs_to_list(environment_variables: dict[str, str]) -> list[str]: """Converts a dictionary environment variables to a list of parameters that can be passed to the container exec/run commands to ensure those env variables are available in the container. Args: - environment_variables (Dict[str, str]): A dictionary of environment variables and + environment_variables (dict[str, str]): A dictionary of environment variables and their values. + Returns: - List[str]: The list of parameters to use when running a hybrid job that will include the + list[str]: The list of parameters to use when running a hybrid job that will include the provided environment variables as part of the runtime. """ env_list = [] @@ -84,12 +85,12 @@ def _envs_to_list(environment_variables: Dict[str, str]) -> List[str]: return env_list @staticmethod - def _check_output_formatted(command: List[str]) -> str: + def _check_output_formatted(command: list[str]) -> str: """This is a wrapper around the subprocess.check_output command that decodes the output to UTF-8 encoding. Args: - command(List[str]): The command to run. + command(list[str]): The command to run. Returns: str: The UTF-8 encoded output of running the command. @@ -103,6 +104,9 @@ def _login_to_ecr(self, account_id: str, ecr_url: str) -> None: Args: account_id(str): The customer account ID. ecr_url(str): The URL of the ECR repo to log into. + + Raises: + ValueError: Invalid permissions to pull container. """ ecr_client = self._aws_session.ecr_client authorization_data_result = ecr_client.get_authorization_token(registryIds=[account_id]) @@ -121,6 +125,9 @@ def _pull_image(self, image_uri: str) -> None: Args: image_uri(str): The URI of the ECR image to pull. + + Raises: + ValueError: Invalid ECR URL. """ ecr_pattern = re.compile(self.ECR_URI_PATTERN) ecr_pattern_match = ecr_pattern.match(image_uri) @@ -145,6 +152,9 @@ def _start_container(self, image_uri: str, force_update: bool) -> str: image_uri(str): The URI of the ECR image to run. force_update(bool): Do a docker pull, even if the image is local, in order to update. + Raises: + ValueError: Invalid local image URI. + Returns: str: The name of the running container, which can be used to execute further commands. """ @@ -230,13 +240,16 @@ def copy_from(self, source: str, destination: str) -> None: def run_local_job( self, - environment_variables: Dict[str, str], + environment_variables: dict[str, str], ) -> None: """Runs a Braket Hybrid job in a local container. Args: - environment_variables (Dict[str, str]): The environment variables to make available + environment_variables (dict[str, str]): The environment variables to make available as part of running the hybrid job. + + Raises: + ValueError: `start_program_name` is not found. """ start_program_name = self._check_output_formatted( ["docker", "exec", self._container_name, "printenv", "SAGEMAKER_PROGRAM"] diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 7505dcbf5..57c1f3653 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -13,17 +13,18 @@ import json import tempfile +from collections.abc import Iterable from logging import Logger, getLogger from pathlib import Path -from typing import Any, Dict, Iterable +from typing import Any from braket.aws.aws_session import AwsSession from braket.jobs.local.local_job_container import _LocalJobContainer def setup_container( - container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs -) -> Dict[str, str]: + container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs: str +) -> dict[str, str]: """Sets up a container with prerequisites for running a Braket Hybrid Job. The prerequisites are based on the options the customer has chosen for the hybrid job. Similarly, any environment variables that are needed during runtime will be returned by this function. @@ -31,9 +32,10 @@ def setup_container( Args: container(_LocalJobContainer): The container that will run the braket hybrid job. aws_session (AwsSession): AwsSession for connecting to AWS Services. + **creation_kwargs (str): Arbitrary keyword arguments. Returns: - Dict[str, str]: A dictionary of environment variables that reflect Braket Hybrid Jobs + dict[str, str]: A dictionary of environment variables that reflect Braket Hybrid Jobs options requested by the customer. """ logger = getLogger(__name__) @@ -51,17 +53,18 @@ def setup_container( return run_environment_variables -def _create_expected_paths(container: _LocalJobContainer, **creation_kwargs) -> None: +def _create_expected_paths(container: _LocalJobContainer, **creation_kwargs: str) -> None: """Creates the basic paths required for Braket Hybrid Jobs to run. Args: container(_LocalJobContainer): The container that will run the braket hybrid job. + **creation_kwargs (str): Arbitrary keyword arguments. """ container.makedir("/opt/ml/model") container.makedir(creation_kwargs["checkpointConfig"]["localPath"]) -def _get_env_credentials(aws_session: AwsSession, logger: Logger) -> Dict[str, str]: +def _get_env_credentials(aws_session: AwsSession, logger: Logger) -> dict[str, str]: """Gets the account credentials from boto so they can be added as environment variables to the running container. @@ -70,7 +73,7 @@ def _get_env_credentials(aws_session: AwsSession, logger: Logger) -> Dict[str, s logger (Logger): Logger object with which to write logs. Default is `getLogger(__name__)` Returns: - Dict[str, str]: The set of key/value pairs that should be added as environment variables + dict[str, str]: The set of key/value pairs that should be added as environment variables to the running container. """ credentials = aws_session.boto_session.get_credentials() @@ -90,15 +93,15 @@ def _get_env_credentials(aws_session: AwsSession, logger: Logger) -> Dict[str, s } -def _get_env_script_mode_config(script_mode_config: Dict[str, str]) -> Dict[str, str]: +def _get_env_script_mode_config(script_mode_config: dict[str, str]) -> dict[str, str]: """Gets the environment variables related to the customer script mode config. Args: - script_mode_config (Dict[str, str]): The values for scriptModeConfig in the boto3 input + script_mode_config (dict[str, str]): The values for scriptModeConfig in the boto3 input parameters for running a Braket Hybrid Job. Returns: - Dict[str, str]: The set of key/value pairs that should be added as environment variables + dict[str, str]: The set of key/value pairs that should be added as environment variables to the running container. """ result = { @@ -110,15 +113,16 @@ def _get_env_script_mode_config(script_mode_config: Dict[str, str]) -> Dict[str, return result -def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> Dict[str, str]: +def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs: str) -> dict[str, str]: """This function gets the remaining 'simple' env variables, that don't require any additional logic to determine what they are or when they should be added as env variables. Args: aws_session (AwsSession): AwsSession for connecting to AWS Services. + **creation_kwargs (str): Arbitrary keyword arguments. Returns: - Dict[str, str]: The set of key/value pairs that should be added as environment variables + dict[str, str]: The set of key/value pairs that should be added as environment variables to the running container. """ job_name = creation_kwargs["jobName"] @@ -135,12 +139,12 @@ def _get_env_default_vars(aws_session: AwsSession, **creation_kwargs) -> Dict[st } -def _get_env_hyperparameters() -> Dict[str, str]: +def _get_env_hyperparameters() -> dict[str, str]: """Gets the env variable for hyperparameters. This should only be added if the customer has provided hyperpameters to the hybrid job. Returns: - Dict[str, str]: The set of key/value pairs that should be added as environment variables + dict[str, str]: The set of key/value pairs that should be added as environment variables to the running container. """ return { @@ -148,12 +152,12 @@ def _get_env_hyperparameters() -> Dict[str, str]: } -def _get_env_input_data() -> Dict[str, str]: +def _get_env_input_data() -> dict[str, str]: """Gets the env variable for input data. This should only be added if the customer has provided input data to the hybrid job. Returns: - Dict[str, str]: The set of key/value pairs that should be added as environment variables + dict[str, str]: The set of key/value pairs that should be added as environment variables to the running container. """ return { @@ -161,12 +165,13 @@ def _get_env_input_data() -> Dict[str, str]: } -def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs) -> bool: +def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs: str) -> bool: """If hyperpameters are present, this function will store them as a JSON object in the container in the appropriate location on disk. Args: container(_LocalJobContainer): The container to save hyperparameters to. + **creation_kwargs (str): Arbitrary keyword arguments. Returns: bool: True if any hyperparameters were copied to the container. @@ -185,15 +190,20 @@ def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs) -> b def _download_input_data( aws_session: AwsSession, download_dir: str, - input_data: Dict[str, Any], + input_data: dict[str, Any], ) -> None: """Downloads input data for a hybrid job. Args: aws_session (AwsSession): AwsSession for connecting to AWS Services. download_dir (str): The directory path to download to. - input_data (Dict[str, Any]): One of the input data in the boto3 input parameters for + input_data (dict[str, Any]): One of the input data in the boto3 input parameters for running a Braket Hybrid Job. + + Raises: + ValueError: File already exists. + RuntimeError: The item is not found. + """ # If s3 prefix is the full name of a directory and all keys are inside # that directory, the contents of said directory will be copied into a @@ -243,7 +253,7 @@ def _is_dir(prefix: str, keys: Iterable[str]) -> bool: def _copy_input_data_list( - container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs + container: _LocalJobContainer, aws_session: AwsSession, **creation_kwargs: str ) -> bool: """If the input data list is not empty, this function will download the input files and store them in the container. @@ -251,6 +261,7 @@ def _copy_input_data_list( Args: container (_LocalJobContainer): The container to save input data to. aws_session (AwsSession): AwsSession for connecting to AWS Services. + **creation_kwargs (str): Arbitrary keyword arguments. Returns: bool: True if any input data was copied to the container. diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 734d51123..5e2f12f82 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -14,30 +14,31 @@ import collections import os import sys +from collections.abc import Generator ############################################################################## # # Support for reading logs # ############################################################################## -from typing import Dict, List, Optional, Tuple +from typing import ClassVar, Optional from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession -class ColorWrap(object): +class ColorWrap: """A callable that prints text in a different color depending on the instance. Up to 5 if the standard output is a terminal or a Jupyter notebook cell. """ # For what color each number represents, see # https://misc.flogisoft.com/bash/tip_colors_and_formatting#colors - _stream_colors = [34, 35, 32, 36, 33] + _stream_colors: ClassVar = [34, 35, 32, 36, 33] - def __init__(self, force=False): - """Initialize the class. + def __init__(self, force: bool = False): + """Initialize a `ColorWrap`. Args: force (bool): If True, the render output is colorized wherever the @@ -45,7 +46,7 @@ def __init__(self, force=False): """ self.colorize = force or sys.stdout.isatty() or os.environ.get("JPY_PARENT_PID", None) - def __call__(self, index, s): + def __call__(self, index: int, s: str): """Prints the string, colorized or not, depending on the environment. Args: @@ -73,8 +74,8 @@ def _color_wrap(self, index: int, s: str) -> None: def multi_stream_iter( - aws_session: AwsSession, log_group: str, streams: List[str], positions: Dict[str, Position] -) -> Tuple[int, Dict]: + aws_session: AwsSession, log_group: str, streams: list[str], positions: dict[str, Position] +) -> Generator[tuple[int, dict]]: """Iterates over the available events coming from a set of log streams. Log streams are in a single log group interleaving the events from each stream, so they yield in timestamp order. @@ -82,13 +83,13 @@ def multi_stream_iter( Args: aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. log_group (str): The name of the log group. - streams (List[str]): A list of the log stream names. The the stream number is + streams (list[str]): A list of the log stream names. The the stream number is the position of the stream in this list. - positions (Dict[str, Position]): A list of (timestamp, skip) pairs which represent + positions (dict[str, Position]): A list of (timestamp, skip) pairs which represent the last record read from each stream. Yields: - Tuple[int, Dict]: A tuple of (stream number, cloudwatch log event). + Generator[tuple[int, dict]]: A tuple of (stream number, cloudwatch log event). """ event_iters = [ log_stream(aws_session, log_group, s, positions[s].timestamp, positions[s].skip) @@ -112,7 +113,7 @@ def multi_stream_iter( def log_stream( aws_session: AwsSession, log_group: str, stream_name: str, start_time: int = 0, skip: int = 0 -) -> Dict: +) -> Generator[dict]: """A generator for log items in a single stream. This yields all the items that are available at the current moment. @@ -125,12 +126,11 @@ def log_stream( when there are multiple entries at the same timestamp.) Yields: - Dict: A CloudWatch log event with the following key-value pairs: + Generator[dict]: A CloudWatch log event with the following key-value pairs: 'timestamp' (int): The time of the event. 'message' (str): The log event data. 'ingestionTime' (int): The time the event was ingested. """ - next_token = None event_count = 1 @@ -151,16 +151,15 @@ def log_stream( else: skip = skip - event_count events = [] - for ev in events: - yield ev + yield from events def flush_log_streams( # noqa C901 aws_session: AwsSession, log_group: str, stream_prefix: str, - stream_names: List[str], - positions: Dict[str, Position], + stream_names: list[str], + positions: dict[str, Position], stream_count: int, has_streams: bool, color_wrap: ColorWrap, @@ -173,11 +172,11 @@ def flush_log_streams( # noqa C901 aws_session (AwsSession): The AwsSession for interfacing with CloudWatch. log_group (str): The name of the log group. stream_prefix (str): The prefix for log streams to flush. - stream_names (List[str]): A list of the log stream names. The position of the stream in + stream_names (list[str]): A list of the log stream names. The position of the stream in this list is the stream number. If incomplete, the function will check for remaining streams and mutate this list to add stream names when available, up to the `stream_count` limit. - positions (Dict[str, Position]): A dict mapping stream numbers to (timestamp, skip) pairs + positions (dict[str, Position]): A dict mapping stream numbers to (timestamp, skip) pairs which represent the last record read from each stream. The function will update this list after being called to represent the new last record read from each stream. stream_count (int): The number of streams expected. @@ -189,6 +188,9 @@ def flush_log_streams( # noqa C901 queue_position (Optional[str]): The current queue position. This is not passed in if the job is ran with `quiet=True` + Raises: + Exception: Any exception found besides a ResourceNotFoundException. + Returns: bool: Returns 'True' if any streams have been flushed. """ diff --git a/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py index 462501cb6..991370f35 100644 --- a/src/braket/jobs/metrics.py +++ b/src/braket/jobs/metrics.py @@ -21,19 +21,17 @@ def log_metric( timestamp: Optional[float] = None, iteration_number: Optional[int] = None, ) -> None: - """ - Records Braket Hybrid Job metrics. + """Records Braket Hybrid Job metrics. Args: - metric_name (str) : The name of the metric. + metric_name (str): The name of the metric. - value (Union[float, int]) : The value of the metric. + value (Union[float, int]): The value of the metric. - timestamp (Optional[float]) : The time the metric data was received, expressed - as the number of seconds - since the epoch. Default: Current system time. + timestamp (Optional[float]): The time the metric data was received, expressed + as the number of seconds since the epoch. Default: Current system time. - iteration_number (Optional[int]) : The iteration number of the metric. + iteration_number (Optional[int]): The iteration number of the metric. """ logged_timestamp = timestamp or time.time() metric_list = [f"Metrics - timestamp={logged_timestamp}; {metric_name}={value};"] diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index b32cd6b9c..be4e72541 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -15,7 +15,7 @@ import time from logging import Logger, getLogger -from typing import Any, Dict, List, Optional, Union +from typing import Any, Optional, Union from braket.aws.aws_session import AwsSession from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType @@ -23,7 +23,7 @@ from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser -class CwlInsightsMetricsFetcher(object): +class CwlInsightsMetricsFetcher: LOG_GROUP_NAME = "/aws/braket/jobs" QUERY_DEFAULT_JOB_DURATION = 3 * 60 * 60 @@ -34,7 +34,8 @@ def __init__( poll_interval_seconds: float = 1, logger: Logger = getLogger(__name__), ): - """ + """Initializes a `CwlInsightsMetricsFetcher`. + Args: aws_session (AwsSession): AwsSession to connect to AWS with. poll_timeout_seconds (float): The polling timeout for retrieving the metrics, @@ -52,32 +53,33 @@ def __init__( @staticmethod def _get_element_from_log_line( - element_name: str, log_line: List[Dict[str, Any]] + element_name: str, log_line: list[dict[str, Any]] ) -> Optional[str]: - """ - Finds and returns an element of a log line from CloudWatch Insights results. + """Finds and returns an element of a log line from CloudWatch Insights results. Args: element_name (str): The element to find. - log_line (List[Dict[str, Any]]): An iterator for RegEx matches on a log line. + log_line (list[dict[str, Any]]): An iterator for RegEx matches on a log line. Returns: - Optional[str] : The value of the element with the element name, or None if no such + Optional[str]: The value of the element with the element name, or None if no such element is found. """ return next( (element["value"] for element in log_line if element["field"] == element_name), None ) - def _get_metrics_results_sync(self, query_id: str) -> List[Any]: - """ - Waits for the CloudWatch Insights query to complete and then returns all the results. + def _get_metrics_results_sync(self, query_id: str) -> list[Any]: + """Waits for the CloudWatch Insights query to complete and then returns all the results. Args: query_id (str): CloudWatch Insights query ID. + Raises: + MetricsRetrievalError: Raised if the query is Failed or Cancelled. + Returns: - List[Any]: The results from CloudWatch insights 'GetQueryResults' operation. + list[Any]: The results from CloudWatch insights 'GetQueryResults' operation. """ timeout_time = time.time() + self._poll_timeout_seconds while time.time() < timeout_time: @@ -92,13 +94,12 @@ def _get_metrics_results_sync(self, query_id: str) -> List[Any]: self._logger.warning(f"Timed out waiting for query {query_id}.") return [] - def _parse_log_line(self, result_entry: List[Dict[str, Any]], parser: LogMetricsParser) -> None: - """ - Parses the single entry from CloudWatch Insights results and adds any metrics it finds + def _parse_log_line(self, result_entry: list[dict[str, Any]], parser: LogMetricsParser) -> None: + """Parses the single entry from CloudWatch Insights results and adds any metrics it finds to 'all_metrics' along with the timestamp for the entry. Args: - result_entry (List[Dict[str, Any]]): A structured result from calling CloudWatch + result_entry (list[dict[str, Any]]): A structured result from calling CloudWatch Insights to get logs that contain metrics. A single entry contains the message (the actual line logged to output), the timestamp (generated by CloudWatch Logs), and other metadata that we (currently) do not use. @@ -110,20 +111,19 @@ def _parse_log_line(self, result_entry: List[Dict[str, Any]], parser: LogMetrics parser.parse_log_message(timestamp, message) def _parse_log_query_results( - self, results: List[Any], metric_type: MetricType, statistic: MetricStatistic - ) -> Dict[str, List[Union[str, float, int]]]: - """ - Parses CloudWatch Insights results and returns all found metrics. + self, results: list[Any], metric_type: MetricType, statistic: MetricStatistic + ) -> dict[str, list[Union[str, float, int]]]: + """Parses CloudWatch Insights results and returns all found metrics. Args: - results (List[Any]): A structured result from calling CloudWatch Insights to get + results (list[Any]): A structured result from calling CloudWatch Insights to get logs that contain metrics. metric_type (MetricType): The type of metrics to get. statistic (MetricStatistic): The statistic to determine which metric value to use when there is a conflict. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + dict[str, list[Union[str, float, int]]]: The metrics data. """ parser = LogMetricsParser() for result in results: @@ -137,9 +137,8 @@ def get_metrics_for_job( statistic: MetricStatistic = MetricStatistic.MAX, job_start_time: int | None = None, job_end_time: int | None = None, - ) -> Dict[str, List[Union[str, float, int]]]: - """ - Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. + ) -> dict[str, list[Union[str, float, int]]]: + """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the @@ -153,7 +152,7 @@ def get_metrics_for_job( which the hybrid job finished. Default: current time. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data, where the keys + dict[str, list[Union[str, float, int]]]: The metrics data, where the keys are the column names and the values are a list containing the values in each row. Example: diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index 5e3ef28f2..8a5a5333d 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -13,14 +13,14 @@ import time from logging import Logger, getLogger -from typing import Dict, List, Union +from typing import Union from braket.aws.aws_session import AwsSession from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType from braket.jobs.metrics_data.log_metrics_parser import LogMetricsParser -class CwlMetricsFetcher(object): +class CwlMetricsFetcher: LOG_GROUP_NAME = "/aws/braket/jobs" def __init__( @@ -29,7 +29,8 @@ def __init__( poll_timeout_seconds: float = 10, logger: Logger = getLogger(__name__), ): - """ + """Initializes a `CwlMetricsFetcher`. + Args: aws_session (AwsSession): AwsSession to connect to AWS with. poll_timeout_seconds (float): The polling timeout for retrieving the metrics, @@ -44,8 +45,7 @@ def __init__( @staticmethod def _is_metrics_message(message: str) -> bool: - """ - Returns true if a given message is designated as containing Metrics. + """Returns true if a given message is designated as containing Metrics. Args: message (str): The message to check. @@ -63,8 +63,7 @@ def _parse_metrics_from_log_stream( timeout_time: float, parser: LogMetricsParser, ) -> None: - """ - Synchronously retrieves the algorithm metrics logged in a given hybrid job log stream. + """Synchronously retrieves the algorithm metrics logged in a given hybrid job log stream. Args: stream_name (str): The name of the log stream. @@ -93,16 +92,16 @@ def _parse_metrics_from_log_stream( kwargs["nextToken"] = next_token self._logger.warning("Timed out waiting for all metrics. Data may be incomplete.") - def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> List[str]: - """ - Retrieves the list of log streams relevant to a hybrid job. + def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> list[str]: + """Retrieves the list of log streams relevant to a hybrid job. Args: job_name (str): The name of the hybrid job. timeout_time (float) : Metrics cease getting streamed if the current time exceeds the timeout time. + Returns: - List[str] : A list of log stream names for the given hybrid job. + list[str]: A list of log stream names for the given hybrid job. """ kwargs = { "logGroupName": self.LOG_GROUP_NAME, @@ -129,9 +128,8 @@ def get_metrics_for_job( job_name: str, metric_type: MetricType = MetricType.TIMESTAMP, statistic: MetricStatistic = MetricStatistic.MAX, - ) -> Dict[str, List[Union[str, float, int]]]: - """ - Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. + ) -> dict[str, list[Union[str, float, int]]]: + """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: job_name (str): The name of the Hybrid Job. The name must be exact to ensure only the @@ -141,7 +139,7 @@ def get_metrics_for_job( when there is a conflict. Default is MetricStatistic.MAX. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data, where the keys + dict[str, list[Union[str, float, int]]]: The metrics data, where the keys are the column names and the values are a list containing the values in each row. Example: diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index 7187486c7..82142a589 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -12,15 +12,15 @@ # language governing permissions and limitations under the License. import re +from collections.abc import Iterator from logging import Logger, getLogger -from typing import Dict, Iterator, List, Optional, Tuple, Union +from typing import Optional, Union from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType -class LogMetricsParser(object): - """ - This class is used to parse metrics from log lines, and return them in a more +class LogMetricsParser: + """This class is used to parse metrics from log lines, and return them in a more convenient format. """ @@ -43,8 +43,7 @@ def _get_value( new_value: Union[str, float, int], statistic: MetricStatistic, ) -> Union[str, float, int]: - """ - Gets the value based on a statistic. + """Gets the value based on a statistic. Args: current_value (Optional[Union[str, float, int]]): The current value. @@ -64,15 +63,14 @@ def _get_value( def _get_metrics_from_log_line_matches( self, all_matches: Iterator - ) -> Dict[str, Union[str, float, int]]: - """ - Converts matches from a RegEx to a set of metrics. + ) -> dict[str, Union[str, float, int]]: + """Converts matches from a RegEx to a set of metrics. Args: all_matches (Iterator): An iterator for RegEx matches on a log line. Returns: - Dict[str, Union[str, float, int]]: The set of metrics found by the RegEx. The result + dict[str, Union[str, float, int]]: The set of metrics found by the RegEx. The result is in the format { : }. This implies that multiple metrics with the same name are deduped to the last instance of that metric. """ @@ -87,8 +85,7 @@ def _get_metrics_from_log_line_matches( return metrics def parse_log_message(self, timestamp: str, message: str) -> None: - """ - Parses a line from logs, adding all the metrics that have been logged + """Parses a line from logs, adding all the metrics that have been logged on that line. The timestamp is also added to match the corresponding values. Args: @@ -111,19 +108,19 @@ def parse_log_message(self, timestamp: str, message: str) -> None: def get_columns_and_pivot_indices( self, pivot: str - ) -> Tuple[Dict[str, List[Union[str, float, int]]], Dict[Tuple[int, str], int]]: - """ - Parses the metrics to find all the metrics that have the pivot column. The values of the + ) -> tuple[dict[str, list[Union[str, float, int]]], dict[tuple[int, str], int]]: + """Parses the metrics to find all the metrics that have the pivot column. The values of the pivot column are paired with the node_id and assigned a row index, so that all metrics with the same pivot value and node_id are stored in the same row. + Args: pivot (str): The name of the pivot column. Must be TIMESTAMP or ITERATION_NUMBER. Returns: - Tuple[Dict[str, List[Union[str, float, int]]], Dict[Tuple[int, str], int]]: Contains: - The Dict[str, List[Any]] is the result table with all the metrics values initialized + tuple[dict[str, list[Union[str, float, int]]], dict[tuple[int, str], int]]: Contains: + The dict[str, list[Any]] is the result table with all the metrics values initialized to None. - The Dict[Tuple[int, str], int] is the list of pivot indices, where the value of a + The dict[tuple[int, str], int] is the list of pivot indices, where the value of a pivot column and node_id is mapped to a row index. """ row_count = 0 @@ -144,9 +141,8 @@ def get_columns_and_pivot_indices( def get_metric_data_with_pivot( self, pivot: str, statistic: MetricStatistic - ) -> Dict[str, List[Union[str, float, int]]]: - """ - Gets the metric data for a given pivot column name. Metrics without the pivot column + ) -> dict[str, list[Union[str, float, int]]]: + """Gets the metric data for a given pivot column name. Metrics without the pivot column are not included in the results. Metrics that have the same value in the pivot column from the same node are returned in the same row. Metrics from different nodes are stored in different rows. If the metric has multiple values for the row, the statistic is used @@ -169,7 +165,7 @@ def get_metric_data_with_pivot( statistic (MetricStatistic): The statistic to determine which value to use. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + dict[str, list[Union[str, float, int]]]: The metrics data. """ table, pivot_indices = self.get_columns_and_pivot_indices(pivot) for metric in self.all_metrics: @@ -184,9 +180,8 @@ def get_metric_data_with_pivot( def get_parsed_metrics( self, metric_type: MetricType, statistic: MetricStatistic - ) -> Dict[str, List[Union[str, float, int]]]: - """ - Gets all the metrics data, where the keys are the column names and the values are a list + ) -> dict[str, list[Union[str, float, int]]]: + """Gets all the metrics data, where the keys are the column names and the values are a list containing the values in each row. Args: @@ -196,7 +191,7 @@ def get_parsed_metrics( when there is a conflict. Returns: - Dict[str, List[Union[str, float, int]]] : The metrics data. + dict[str, list[Union[str, float, int]]]: The metrics data. Example: timestamp energy diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py index a84118991..32c660bcf 100644 --- a/src/braket/jobs/quantum_job.py +++ b/src/braket/jobs/quantum_job.py @@ -13,7 +13,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Dict, List +from typing import Any from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType @@ -26,6 +26,7 @@ class QuantumJob(ABC): @abstractmethod def arn(self) -> str: """The ARN (Amazon Resource Name) of the hybrid job. + Returns: str: The ARN (Amazon Resource Name) of the hybrid job. """ @@ -34,6 +35,7 @@ def arn(self) -> str: @abstractmethod def name(self) -> str: """The name of the hybrid job. + Returns: str: The name of the hybrid job. """ @@ -47,6 +49,7 @@ def state(self, use_cached_value: bool = False) -> str: value from the Amazon Braket `GetJob` operation. If `False`, calls the `GetJob` operation to retrieve metadata, which also updates the cached value. Default = `False`. + Returns: str: The value of `status` in `metadata()`. This is the value of the `status` key in the Amazon Braket `GetJob` operation. @@ -95,7 +98,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: # Cloudwatch after the job was marked complete. @abstractmethod - def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: + def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: """Gets the job metadata defined in Amazon Braket. Args: @@ -103,8 +106,9 @@ def metadata(self, use_cached_value: bool = False) -> Dict[str, Any]: from the Amazon Braket `GetJob` operation, if it exists; if does not exist, `GetJob` is called to retrieve the metadata. If `False`, always calls `GetJob`, which also updates the cached value. Default: `False`. + Returns: - Dict[str, Any]: Dict that specifies the hybrid job metadata defined in Amazon Braket. + dict[str, Any]: Dict that specifies the hybrid job metadata defined in Amazon Braket. """ @abstractmethod @@ -112,7 +116,7 @@ def metrics( self, metric_type: MetricType = MetricType.TIMESTAMP, statistic: MetricStatistic = MetricStatistic.MAX, - ) -> Dict[str, List[Any]]: + ) -> dict[str, list[Any]]: """Gets all the metrics data, where the keys are the column names, and the values are a list containing the values in each row. @@ -123,7 +127,7 @@ def metrics( when there is a conflict. Default: MetricStatistic.MAX. Returns: - Dict[str, List[Any]] : The metrics data. + dict[str, list[Any]]: The metrics data. Example: timestamp energy @@ -150,7 +154,7 @@ def result( self, poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Retrieves the hybrid job result persisted using save_job_result() function. Args: @@ -162,7 +166,7 @@ def result( Returns: - Dict[str, Any]: Dict specifying the hybrid job results. + dict[str, Any]: Dict specifying the hybrid job results. Raises: RuntimeError: if hybrid job is in a FAILED or CANCELLED state. diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 9e18faeab..98905dc56 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -258,8 +258,7 @@ def prepare_quantum_job( def _generate_default_job_name( image_uri: str | None = None, func: Callable | None = None, timestamp: int | str | None = None ) -> str: - """ - Generate default job name using the image uri and entrypoint function. + """Generate default job name using the image uri and entrypoint function. Args: image_uri (str | None): URI for the image container. @@ -277,25 +276,25 @@ def _generate_default_job_name( if len(name) + len(timestamp) > max_length: name = name[: max_length - len(timestamp) - 1] warnings.warn( - f"Job name exceeded {max_length} characters. Truncating name to {name}-{timestamp}." + f"Job name exceeded {max_length} characters. " + f"Truncating name to {name}-{timestamp}.", + stacklevel=1, ) + elif not image_uri: + name = "braket-job-default" else: - if not image_uri: - name = "braket-job-default" - else: - job_type_match = re.search("/amazon-braket-(.*)-jobs:", image_uri) or re.search( - "/amazon-braket-([^:/]*)", image_uri - ) - container = f"-{job_type_match.groups()[0]}" if job_type_match else "" - name = f"braket-job{container}" + job_type_match = re.search("/amazon-braket-(.*)-jobs:", image_uri) or re.search( + "/amazon-braket-([^:/]*)", image_uri + ) + container = f"-{job_type_match.groups()[0]}" if job_type_match else "" + name = f"braket-job{container}" return f"{name}-{timestamp}" def _process_s3_source_module( source_module: str, entry_point: str, aws_session: AwsSession, code_location: str ) -> None: - """ - Check that the source module is an S3 URI of the correct type and that entry point is + """Check that the source module is an S3 URI of the correct type and that entry point is provided. Args: @@ -304,6 +303,9 @@ def _process_s3_source_module( aws_session (AwsSession): AwsSession to copy source module to code location. code_location (str): S3 URI pointing to the location where the code will be copied to. + + Raises: + ValueError: The entry point is None or does not end with .tar.gz. """ if entry_point is None: raise ValueError("If source_module is an S3 URI, entry_point must be provided.") @@ -318,9 +320,9 @@ def _process_s3_source_module( def _process_local_source_module( source_module: str, entry_point: str, aws_session: AwsSession, code_location: str ) -> str: - """ - Check that entry point is valid with respect to source module, or provide a default + """Check that entry point is valid with respect to source module, or provide a default value if entry point is not given. Tar and upload source module to code location in S3. + Args: source_module (str): Local path pointing to the source module. entry_point (str): Entry point relative to the source module. @@ -328,6 +330,9 @@ def _process_local_source_module( code_location (str): S3 URI pointing to the location where the code will be uploaded to. + Raises: + ValueError: Raised if the source module file is not found. + Returns: str: Entry point. """ @@ -344,12 +349,14 @@ def _process_local_source_module( def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: - """ - Confirm that a valid entry point relative to source module is given. + """Confirm that a valid entry point relative to source module is given. Args: source_module_path (Path): Path to source module. entry_point (str): Entry point relative to source module. + + Raises: + ValueError: Raised if the module was not found. """ importable, _, _method = entry_point.partition(":") sys.path.append(str(source_module_path.parent)) @@ -357,7 +364,8 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: # second argument allows relative imports importlib.invalidate_caches() module = importlib.util.find_spec(importable, source_module_path.stem) - assert module is not None + if module is None: + raise AssertionError # if entry point is nested (ie contains '.'), parent modules are imported except (ModuleNotFoundError, AssertionError): raise ValueError(f"Entry point module was not found: {importable}") @@ -368,8 +376,7 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: def _tar_and_upload_to_code_location( source_module_path: Path, aws_session: AwsSession, code_location: str ) -> None: - """ - Tar and upload source module to code location. + """Tar and upload source module to code location. Args: source_module_path (Path): Path to source module. @@ -384,12 +391,14 @@ def _tar_and_upload_to_code_location( def _validate_params(dict_arr: dict[str, tuple[any, any]]) -> None: - """ - Validate that config parameters are of the right type. + """Validate that config parameters are of the right type. Args: dict_arr (dict[str, tuple[any, any]]): dict mapping parameter names to a tuple containing the provided value and expected type. + + Raises: + ValueError: If the user_input is not the same as the expected data type. """ for parameter_name, value_tuple in dict_arr.items(): user_input, expected_datatype = value_tuple @@ -407,8 +416,8 @@ def _process_input_data( aws_session: AwsSession, subdirectory: str, ) -> list[dict[str, Any]]: - """ - Convert input data into a list of dicts compatible with the Braket API. + """Convert input data into a list of dicts compatible with the Braket API. + Args: input_data (str | dict | S3DataSourceConfig): Either a channel definition or a dictionary mapping channel names to channel definitions, where a channel definition @@ -437,8 +446,8 @@ def _process_channel( channel_name: str, subdirectory: str, ) -> S3DataSourceConfig: - """ - Convert a location to an S3DataSourceConfig, uploading local data to S3, if necessary. + """Convert a location to an S3DataSourceConfig, uploading local data to S3, if necessary. + Args: location (str): Local prefix or S3 prefix. job_name (str): Hybrid job name. @@ -469,8 +478,7 @@ def _process_channel( def _convert_input_to_config(input_data: dict[str, S3DataSourceConfig]) -> list[dict[str, Any]]: - """ - Convert a dictionary mapping channel names to S3DataSourceConfigs into a list of channel + """Convert a dictionary mapping channel names to S3DataSourceConfigs into a list of channel configs compatible with the Braket API. Args: diff --git a/src/braket/jobs/serialization.py b/src/braket/jobs/serialization.py index f8c854d03..179a44970 100644 --- a/src/braket/jobs/serialization.py +++ b/src/braket/jobs/serialization.py @@ -13,26 +13,25 @@ import codecs import pickle -from typing import Any, Dict +from typing import Any from braket.jobs_data import PersistedJobDataFormat def serialize_values( - data_dictionary: Dict[str, Any], data_format: PersistedJobDataFormat -) -> Dict[str, Any]: - """ - Serializes the `data_dictionary` values to the format specified by `data_format`. + data_dictionary: dict[str, Any], data_format: PersistedJobDataFormat +) -> dict[str, Any]: + """Serializes the `data_dictionary` values to the format specified by `data_format`. Args: - data_dictionary (Dict[str, Any]): Dict whose values are to be serialized. + data_dictionary (dict[str, Any]): Dict whose values are to be serialized. data_format (PersistedJobDataFormat): The data format used to serialize the values. Note that for `PICKLED` data formats, the values are base64 encoded after serialization, so that they represent valid UTF-8 text and are compatible with `PersistedJobData.json()`. Returns: - Dict[str, Any]: Dict with same keys as `data_dictionary` and values serialized to + dict[str, Any]: Dict with same keys as `data_dictionary` and values serialized to the specified `data_format`. """ return ( @@ -46,18 +45,17 @@ def serialize_values( def deserialize_values( - data_dictionary: Dict[str, Any], data_format: PersistedJobDataFormat -) -> Dict[str, Any]: - """ - Deserializes the `data_dictionary` values from the format specified by `data_format`. + data_dictionary: dict[str, Any], data_format: PersistedJobDataFormat +) -> dict[str, Any]: + """Deserializes the `data_dictionary` values from the format specified by `data_format`. Args: - data_dictionary (Dict[str, Any]): Dict whose values are to be deserialized. + data_dictionary (dict[str, Any]): Dict whose values are to be deserialized. data_format (PersistedJobDataFormat): The data format that the `data_dictionary` values are currently serialized with. Returns: - Dict[str, Any]: Dict with same keys as `data_dictionary` and values deserialized from + dict[str, Any]: Dict with same keys as `data_dictionary` and values deserialized from the specified `data_format` to plaintext. """ return ( diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index db22f5f60..9d5826520 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -22,8 +22,7 @@ class FreeParameter(FreeParameterExpression): - """ - Class 'FreeParameter' + """Class 'FreeParameter' Free parameters can be used in parameterized circuits. Objects that can take a parameter all inherit from :class:'Parameterizable'. The FreeParameter can be swapped in to a circuit @@ -39,8 +38,7 @@ class FreeParameter(FreeParameterExpression): """ def __init__(self, name: str): - """ - Initializes a new :class:'FreeParameter' object. + """Initializes a new :class:'FreeParameter' object. Args: name (str): Name of the :class:'FreeParameter'. Can be a unicode value. @@ -54,14 +52,11 @@ def __init__(self, name: str): @property def name(self) -> str: - """ - str: Name of this parameter. - """ + """str: Name of this parameter.""" return self._name.name def subs(self, parameter_values: dict[str, Number]) -> Union[FreeParameter, Number]: - """ - Substitutes a value in if the parameter exists within the mapping. + """Substitutes a value in if the parameter exists within the mapping. Args: parameter_values (dict[str, Number]): A mapping of parameter to its @@ -79,14 +74,13 @@ def __str__(self): def __hash__(self) -> int: return hash(tuple(self.name)) - def __eq__(self, other): + def __eq__(self, other: FreeParameter): if isinstance(other, FreeParameter): return self._name == other._name return super().__eq__(other) def __repr__(self) -> str: - """ - The representation of the :class:'FreeParameter'. + """The representation of the :class:'FreeParameter'. Returns: str: The name of the class:'FreeParameter' to represent the class. diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index fdfa1469a..d3ad91259 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -25,8 +25,7 @@ class FreeParameterExpression: - """ - Class 'FreeParameterExpression' + """Class 'FreeParameterExpression' Objects that can take a parameter all inherit from :class:'Parameterizable'. FreeParametersExpressions can hold FreeParameters that can later be @@ -35,8 +34,7 @@ class FreeParameterExpression: """ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr, str]): - """ - Initializes a FreeParameterExpression. Best practice is to initialize using + """Initializes a FreeParameterExpression. Best practice is to initialize using FreeParameters and Numbers. Not meant to be initialized directly. Below are examples of how FreeParameterExpressions should be made. @@ -44,6 +42,10 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr Args: expression (Union[FreeParameterExpression, Number, Expr, str]): The expression to use. + Raises: + NotImplementedError: Raised if the expression is not of type + [FreeParameterExpression, Number, Expr, str] + Examples: >>> expression_1 = FreeParameter("theta") * FreeParameter("alpha") >>> expression_2 = 1 + FreeParameter("beta") + 2 * FreeParameter("alpha") @@ -67,6 +69,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr @property def expression(self) -> Union[Number, sympy.Expr]: """Gets the expression. + Returns: Union[Number, Expr]: The expression for the FreeParameterExpression. """ @@ -87,7 +90,7 @@ def subs( Union[FreeParameterExpression, Number, Expr]: A numerical value if there are no symbols left in the expression otherwise returns a new FreeParameterExpression. """ - new_parameter_values = dict() + new_parameter_values = {} for key, val in parameter_values.items(): if issubclass(type(key), FreeParameterExpression): new_parameter_values[key.expression] = val @@ -121,53 +124,52 @@ def _eval_operation(self, node: Any) -> FreeParameterExpression: else: raise ValueError(f"Unsupported string detected: {node}") - def __add__(self, other): + def __add__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression + other.expression) else: return FreeParameterExpression(self.expression + other) - def __radd__(self, other): + def __radd__(self, other: FreeParameterExpression): return FreeParameterExpression(other + self.expression) - def __sub__(self, other): + def __sub__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression - other.expression) else: return FreeParameterExpression(self.expression - other) - def __rsub__(self, other): + def __rsub__(self, other: FreeParameterExpression): return FreeParameterExpression(other - self.expression) - def __mul__(self, other): + def __mul__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression * other.expression) else: return FreeParameterExpression(self.expression * other) - def __rmul__(self, other): + def __rmul__(self, other: FreeParameterExpression): return FreeParameterExpression(other * self.expression) - def __pow__(self, other, modulo=None): + def __pow__(self, other: FreeParameterExpression, modulo: float = None): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) else: return FreeParameterExpression(self.expression**other) - def __rpow__(self, other): + def __rpow__(self, other: FreeParameterExpression): return FreeParameterExpression(other**self.expression) def __neg__(self): return FreeParameterExpression(-1 * self.expression) - def __eq__(self, other): + def __eq__(self, other: FreeParameterExpression): if isinstance(other, FreeParameterExpression): return sympy.sympify(self.expression).equals(sympy.sympify(other.expression)) return False def __repr__(self) -> str: - """ - The representation of the :class:'FreeParameterExpression'. + """The representation of the :class:'FreeParameterExpression'. Returns: str: The expression of the class:'FreeParameterExpression' to represent the class. @@ -199,11 +201,12 @@ def _to_oqpy_expression(self) -> OQPyExpression: return fvar -def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: +def subs_if_free_parameter(parameter: Any, **kwargs: Union[FreeParameterExpression, str]) -> Any: """Substitute a free parameter with the given kwargs, if any. + Args: parameter (Any): The parameter. - ``**kwargs``: The kwargs to use to substitute. + **kwargs (Union[FreeParameterExpression, str]): The kwargs to use to substitute. Returns: Any: The substituted parameters. @@ -217,8 +220,7 @@ def subs_if_free_parameter(parameter: Any, **kwargs) -> Any: def _is_float(argument: str) -> bool: - """ - Checks if a string can be cast into a float. + """Checks if a string can be cast into a float. Args: argument (str): String to check. diff --git a/src/braket/parametric/parameterizable.py b/src/braket/parametric/parameterizable.py index bd5dbc5a7..90c4dc589 100644 --- a/src/braket/parametric/parameterizable.py +++ b/src/braket/parametric/parameterizable.py @@ -21,8 +21,7 @@ class Parameterizable(ABC): - """ - A parameterized object is the abstract definition of an object + """A parameterized object is the abstract definition of an object that can take in FreeParameterExpressions. """ @@ -38,11 +37,13 @@ def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float """ @abstractmethod - def bind_values(self, **kwargs) -> Any: - """ - Takes in parameters and returns an object with specified parameters + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Any: + """Takes in parameters and returns an object with specified parameters replaced with their values. + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + Returns: Any: The result object will depend on the implementation of the object being bound. """ diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 4398d2816..f7ec9d3bf 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -15,7 +15,7 @@ from collections import defaultdict from collections.abc import KeysView from dataclasses import dataclass -from typing import Any, Optional, Union +from typing import Any, ClassVar, Optional, Union import numpy as np from openpulse import ast @@ -50,15 +50,16 @@ class _ParseState: class _ApproximationParser(QASMVisitor[_ParseState]): """Walk the AST and build the output signal amplitude, frequency and phases - for each channel.""" + for each channel. + """ - TIME_UNIT_TO_EXP = {"dt": 4, "ns": 3, "us": 2, "ms": 1, "s": 0} + TIME_UNIT_TO_EXP: ClassVar = {"dt": 4, "ns": 3, "us": 2, "ms": 1, "s": 0} def __init__(self, program: Program, frames: dict[str, Frame]): self.amplitudes = defaultdict(TimeSeries) self.frequencies = defaultdict(TimeSeries) self.phases = defaultdict(TimeSeries) - context = _ParseState(variables=dict(), frame_data=_init_frame_data(frames)) + context = _ParseState(variables={}, frame_data=_init_frame_data(frames)) self._qubit_frames_mapping: dict[str, list[str]] = _init_qubit_frame_mapping(frames) self.visit(program.to_ast(include_externs=False), context) @@ -66,11 +67,13 @@ def visit( self, node: Union[ast.QASMNode, ast.Expression], context: Optional[_ParseState] = None ) -> Any: """Visit a node. + Args: node (Union[ast.QASMNode, ast.Expression]): The node to visit. context (Optional[_ParseState]): The parse state context. + Returns: - Any: The parse return value. + Any: The parsed return value. """ return super().visit(node, context) @@ -103,6 +106,7 @@ def _delay_frame(self, frame_id: str, to_delay_time: float, context: _ParseState def visit_Program(self, node: ast.Program, context: _ParseState = None) -> None: """Visit a Program. + Args: node (ast.Program): The program. context (_ParseState): The parse state context. @@ -112,23 +116,35 @@ def visit_Program(self, node: ast.Program, context: _ParseState = None) -> None: def visit_ExpressionStatement(self, node: ast.ExpressionStatement, context: _ParseState) -> Any: """Visit an Expression. + Args: node (ast.ExpressionStatement): The expression. context (_ParseState): The parse state context. + + Returns: + Any: The parsed return value. """ return self.visit(node.expression, context) # need to check def visit_ClassicalDeclaration( self, node: ast.ClassicalDeclaration, context: _ParseState - ) -> None: + ) -> Union[dict, None]: """Visit a Classical Declaration. node.type, node.identifier, node.init_expression angle[20] a = 1+2; waveform wf = []; port a; + Args: node (ast.ClassicalDeclaration): The classical declaration. context (_ParseState): The parse state context. + + Raises: + NotImplementedError: Raised if the node is not a PortType, FrameType, or + WaveformType. + + Returns: + Union[dict, None]: Returns a dict if WaveformType, None otherwise. """ identifier = self.visit(node.identifier, context) if type(node.type) == ast.WaveformType: @@ -144,6 +160,7 @@ def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseStat """Visit a Delay Instruction. node.duration, node.qubits delay[100ns] $0; + Args: node (ast.DelayInstruction): The classical declaration. context (_ParseState): The parse state context. @@ -168,9 +185,13 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - barrier $0; barrier; barrier frame, frame1; + Args: node (ast.QuantumBarrier): The quantum barrier. context (_ParseState): The parse state context. + + Returns: + None: No return value. """ frames = self._get_frame_parameters(node.qubits, context) if len(frames) == 0: @@ -190,9 +211,13 @@ def visit_FunctionCall(self, node: ast.FunctionCall, context: _ParseState) -> An """Visit a Quantum Barrier. node.name, node.arguments f(args,arg2) + Args: node (ast.FunctionCall): The function call. context (_ParseState): The parse state context. + + Returns: + Any: The parsed return value. """ func_name = node.name.name return getattr(self, func_name)(node, context) @@ -204,6 +229,9 @@ def visit_Identifier(self, node: ast.Identifier, context: _ParseState) -> Any: Args: node (ast.Identifier): The identifier. context (_ParseState): The parse state context. + + Returns: + Any: The parsed return value. """ if node.name in context.variables: return context.variables[node.name] @@ -214,9 +242,16 @@ def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) """Visit Unary Expression. node.op, node.expression ~ ! - + Args: node (ast.UnaryExpression): The unary expression. context (_ParseState): The parse state context. + + Returns: + bool: The parsed boolean operator. + + Raises: + NotImplementedError: Raised for unsupported boolean operators. """ if node.op == ast.UnaryOperator["-"]: return -1 * self.visit(node.expression, context) @@ -234,9 +269,17 @@ def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseStat 1+2 a.b > < >= <= == != && || | ^ & << >> + - * / % ** . + Args: node (ast.BinaryExpression): The binary expression. context (_ParseState): The parse state context. + + Raises: + NotImplementedError: Raised if the binary operator is not in + [> < >= <= == != && || | ^ & << >> + - * / % ** ] + + Returns: + Any: The parsed binary operator. """ lhs = self.visit(node.lhs, context) rhs = self.visit(node.rhs, context) @@ -284,63 +327,85 @@ def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseStat else: raise NotImplementedError - def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: _ParseState) -> Any: + def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: _ParseState) -> list[Any]: """Visit Array Literal. node.values {1,2,4} + Args: node (ast.ArrayLiteral): The array literal. context (_ParseState): The parse state context. + + Returns: + list[Any]: The parsed ArrayLiteral. """ return [self.visit(e, context) for e in node.values] - def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: _ParseState) -> Any: + def visit_IntegerLiteral(self, node: ast.IntegerLiteral, context: _ParseState) -> int: """Visit Integer Literal. node.value 1 Args: node (ast.IntegerLiteral): The integer literal. context (_ParseState): The parse state context. + + Returns: + int: The parsed int value. """ return int(node.value) - def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: _ParseState) -> Any: + def visit_ImaginaryLiteral(self, node: ast.ImaginaryLiteral, context: _ParseState) -> complex: """Visit Imaginary Number Literal. node.value 1.3im Args: - node (ast.visit_ImaginaryLiteral): The imaginary number literal. + node (ast.ImaginaryLiteral): The imaginary number literal. context (_ParseState): The parse state context. + + Returns: + complex: The parsed complex value. """ return complex(node.value * 1j) - def visit_FloatLiteral(self, node: ast.FloatLiteral, context: _ParseState) -> Any: + def visit_FloatLiteral(self, node: ast.FloatLiteral, context: _ParseState) -> float: """Visit Float Literal. node.value 1.1 Args: node (ast.FloatLiteral): The float literal. context (_ParseState): The parse state context. + + Returns: + float: The parsed float value. """ return float(node.value) - def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: _ParseState) -> Any: + def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: _ParseState) -> bool: """Visit Boolean Literal. node.value true Args: node (ast.BooleanLiteral): The boolean literal. context (_ParseState): The parse state context. + + Returns: + bool: The parsed boolean value. """ return True if node.value else False - def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) -> Any: + def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) -> float: """Visit Duration Literal. node.value, node.unit (node.unit.name, node.unit.value) 1 Args: node (ast.DurationLiteral): The duration literal. context (_ParseState): The parse state context. + + Raises: + ValueError: Raised based on time unit not being in `self.TIME_UNIT_TO_EXP`. + + Returns: + float: The duration represented as a float """ if node.unit.name not in self.TIME_UNIT_TO_EXP: raise ValueError(f"Unexpected duration specified: {node.unit.name}:{node.unit.value}") @@ -351,6 +416,7 @@ def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) def set_frequency(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'set_frequency' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -361,6 +427,7 @@ def set_frequency(self, node: ast.FunctionCall, context: _ParseState) -> None: def shift_frequency(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'shift_frequency' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -371,6 +438,7 @@ def shift_frequency(self, node: ast.FunctionCall, context: _ParseState) -> None: def set_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'set_phase' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -381,6 +449,7 @@ def set_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: def shift_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'shift_phase' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -392,6 +461,7 @@ def shift_phase(self, node: ast.FunctionCall, context: _ParseState) -> None: def set_scale(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'set_scale' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -402,6 +472,7 @@ def set_scale(self, node: ast.FunctionCall, context: _ParseState) -> None: def capture_v0(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'capture_v0' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. @@ -410,9 +481,17 @@ def capture_v0(self, node: ast.FunctionCall, context: _ParseState) -> None: def play(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'play' Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. + + Raises: + NotImplementedError: Raises if not of type + [ast.Identifier, ast.FunctionCall, ast.ArrayLiteral] + + Returns: + None: Returns None """ frame_id = self.visit(node.arguments[0], context) if isinstance(node.arguments[1], ast.ArrayLiteral): @@ -436,9 +515,11 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: def constant(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: """A 'constant' Waveform Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. + Returns: Waveform: The waveform object representing the function call. """ @@ -447,9 +528,11 @@ def constant(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: def gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: """A 'gaussian' Waveform Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. + Returns: Waveform: The waveform object representing the function call. """ @@ -458,9 +541,11 @@ def gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: """A 'drag_gaussian' Waveform Function call. + Args: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. + Returns: Waveform: The waveform object representing the function call. """ @@ -469,7 +554,7 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: - frame_states = dict() + frame_states = {} for frameId, frame in frames.items(): frame_states[frameId] = _FrameState( frame.port.dt, frame.frequency, frame.phase % (2 * np.pi) @@ -500,8 +585,10 @@ def _lcm_floats(*dts: list[float]) -> float: Args: *dts (list[float]): list of time resolutions - """ + Returns: + float: The LCM of time increments for a list of frames. + """ sample_rates = [round(1 / dt) for dt in dts] res_gcd = sample_rates[0] for sr in sample_rates[1:]: diff --git a/src/braket/pulse/ast/qasm_parser.py b/src/braket/pulse/ast/qasm_parser.py index 0146eca66..25832aa58 100644 --- a/src/braket/pulse/ast/qasm_parser.py +++ b/src/braket/pulse/ast/qasm_parser.py @@ -40,6 +40,7 @@ def visit_ClassicalDeclaration( angle[20] a = 1+2; waveform wf = []; port a; + Args: node (ast.ClassicalDeclaration): The classical declaration. context (PrinterState): The printer state context. @@ -53,7 +54,7 @@ def ast_to_qasm(ast: ast.Program) -> str: """Converts an AST program to OpenQASM Args: - ast (Program): The AST program. + ast (ast.Program): The AST program. Returns: str: a str representing the OpenPulse program encoding the program. diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index f5e350883..3d7adb4a3 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -18,8 +18,7 @@ class _IRQASMTransformer(QASMTransformer): - """ - QASMTransformer which walks the AST and makes the necessary modifications needed + """QASMTransformer which walks the AST and makes the necessary modifications needed for IR generation. Currently, it performs the following operations: * Replaces capture_v0 function calls with assignment statements, assigning the readout value to a bit register element. @@ -32,8 +31,10 @@ def __init__(self, register_identifier: Optional[str] = None): def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatement) -> Any: """Visit an Expression. + Args: expression_statement (ast.ExpressionStatement): The expression statement. + Returns: Any: The expression statement. """ diff --git a/src/braket/pulse/frame.py b/src/braket/pulse/frame.py index b84e4a440..63700d22e 100644 --- a/src/braket/pulse/frame.py +++ b/src/braket/pulse/frame.py @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + import math from typing import Any, Optional @@ -21,9 +23,9 @@ class Frame: - """ - Frame tracks the frame of reference, when interacting with the qubits, throughout the execution - of a program. See https://openqasm.com/language/openpulse.html#frames for more details. + """Frame tracks the frame of reference, when interacting with the qubits, throughout the + execution of a program. See https://openqasm.com/language/openpulse.html#frames for more + details. """ def __init__( @@ -35,7 +37,8 @@ def __init__( is_predefined: bool = False, properties: Optional[dict[str, Any]] = None, ): - """ + """Initializes a Frame. + Args: frame_id (str): str identifying a unique frame. port (Port): port that this frame is attached to. @@ -58,7 +61,7 @@ def id(self) -> str: """Returns a str indicating the frame id.""" return self._frame_id - def __eq__(self, other) -> bool: + def __eq__(self, other: Frame) -> bool: return ( ( (self.id == other.id) diff --git a/src/braket/pulse/port.py b/src/braket/pulse/port.py index 2b1760415..99b1acca5 100644 --- a/src/braket/pulse/port.py +++ b/src/braket/pulse/port.py @@ -11,6 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + from typing import Any, Optional from oqpy import PortVar @@ -18,13 +20,13 @@ class Port: - """ - Ports represent any input or output component meant to manipulate and observe qubits on + """Ports represent any input or output component meant to manipulate and observe qubits on a device. See https://openqasm.com/language/openpulse.html#ports for more details. """ def __init__(self, port_id: str, dt: float, properties: Optional[dict[str, Any]] = None): - """ + """Initializes a Port. + Args: port_id (str): str identifying a unique port on the device. dt (float): The smallest time step that may be used on the control hardware. @@ -45,7 +47,7 @@ def dt(self) -> float: """Returns the smallest time step that may be used on the control hardware.""" return self._dt - def __eq__(self, other) -> bool: + def __eq__(self, other: Port) -> bool: return self.id == other.id if isinstance(other, Port) else False def _to_oqpy_expression(self) -> OQPyExpression: diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index ac6f49067..e6d78f11f 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -35,8 +35,7 @@ class PulseSequence: - """ - A representation of a collection of instructions to be performed on a quantum device + """A representation of a collection of instructions to be performed on a quantum device and the requested results. """ @@ -70,8 +69,7 @@ def parameters(self) -> set[FreeParameter]: def set_frequency( self, frame: Frame, frequency: Union[float, FreeParameterExpression] ) -> PulseSequence: - """ - Adds an instruction to set the frequency of the frame to the specified `frequency` value. + """Adds an instruction to set the frequency of the frame to the specified `frequency` value. Args: frame (Frame): Frame for which the frequency needs to be set. @@ -81,7 +79,6 @@ def set_frequency( Returns: PulseSequence: self, with the instruction added. """ - _validate_uniqueness(self._frames, frame) self._register_free_parameters(frequency) self._program.set_frequency(frame=frame, freq=frequency) @@ -91,8 +88,8 @@ def set_frequency( def shift_frequency( self, frame: Frame, frequency: Union[float, FreeParameterExpression] ) -> PulseSequence: - """ - Adds an instruction to shift the frequency of the frame by the specified `frequency` value. + """Adds an instruction to shift the frequency of the frame by the specified `frequency` + value. Args: frame (Frame): Frame for which the frequency needs to be shifted. @@ -111,8 +108,7 @@ def shift_frequency( def set_phase( self, frame: Frame, phase: Union[float, FreeParameterExpression] ) -> PulseSequence: - """ - Adds an instruction to set the phase of the frame to the specified `phase` value. + """Adds an instruction to set the phase of the frame to the specified `phase` value. Args: frame (Frame): Frame for which the frequency needs to be set. @@ -131,8 +127,7 @@ def set_phase( def shift_phase( self, frame: Frame, phase: Union[float, FreeParameterExpression] ) -> PulseSequence: - """ - Adds an instruction to shift the phase of the frame by the specified `phase` value. + """Adds an instruction to shift the phase of the frame by the specified `phase` value. Args: frame (Frame): Frame for which the phase needs to be shifted. @@ -151,8 +146,7 @@ def shift_phase( def set_scale( self, frame: Frame, scale: Union[float, FreeParameterExpression] ) -> PulseSequence: - """ - Adds an instruction to set the scale on the frame to the specified `scale` value. + """Adds an instruction to set the scale on the frame to the specified `scale` value. Args: frame (Frame): Frame for which the scale needs to be set. @@ -173,14 +167,14 @@ def delay( qubits_or_frames: Union[Frame, list[Frame], QubitSet], duration: Union[float, FreeParameterExpression], ) -> PulseSequence: - """ - Adds an instruction to advance the frame clock by the specified `duration` value. + """Adds an instruction to advance the frame clock by the specified `duration` value. Args: qubits_or_frames (Union[Frame, list[Frame], QubitSet]): Qubits or frame(s) on which the delay needs to be introduced. duration (Union[float, FreeParameterExpression]): value (in seconds) defining the duration of the delay. + Returns: PulseSequence: self, with the instruction added. """ @@ -193,13 +187,12 @@ def delay( for frame in qubits_or_frames: self._frames[frame.id] = frame else: - physical_qubits = list(PhysicalQubits[int(x)] for x in qubits_or_frames) + physical_qubits = [PhysicalQubits[int(x)] for x in qubits_or_frames] self._program.delay(time=duration, qubits_or_frames=physical_qubits) return self def barrier(self, qubits_or_frames: Union[list[Frame], QubitSet]) -> PulseSequence: - """ - Adds an instruction to align the frame clocks to the latest time across all the specified + """Adds an instruction to align the frame clocks to the latest time across all the specified frames. Args: @@ -215,7 +208,7 @@ def barrier(self, qubits_or_frames: Union[list[Frame], QubitSet]) -> PulseSequen for frame in qubits_or_frames: self._frames[frame.id] = frame else: - physical_qubits = list(PhysicalQubits[int(x)] for x in qubits_or_frames) + physical_qubits = [PhysicalQubits[int(x)] for x in qubits_or_frames] self._program.barrier(qubits_or_frames=physical_qubits) return self @@ -241,8 +234,7 @@ def play(self, frame: Frame, waveform: Waveform) -> PulseSequence: return self def capture_v0(self, frame: Frame) -> PulseSequence: - """ - Adds an instruction to capture the bit output from measuring the specified frame. + """Adds an instruction to capture the bit output from measuring the specified frame. Args: frame (Frame): Frame on which the capture operation needs @@ -258,8 +250,7 @@ def capture_v0(self, frame: Frame) -> PulseSequence: return self def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequence: - """ - Binds FreeParameters based upon their name and values passed in. If parameters + """Binds FreeParameters based upon their name and values passed in. If parameters share the same name, all the parameters of that name will be set to the mapped value. Args: @@ -298,9 +289,9 @@ def make_bound_pulse_sequence(self, param_values: dict[str, float]) -> PulseSequ ]._to_oqpy_expression() new_pulse_sequence._capture_v0_count = self._capture_v0_count - new_pulse_sequence._free_parameters = set( - [p for p in self._free_parameters if p.name not in param_values] - ) + new_pulse_sequence._free_parameters = { + p for p in self._free_parameters if p.name not in param_values + } return new_pulse_sequence @@ -347,8 +338,8 @@ def _parse_arg_from_calibration_schema( self, argument: dict, waveforms: dict[Waveform], frames: dict[Frame] ) -> Any: nonprimitive_arg_type = { - "frame": getattr(frames, "get"), - "waveform": getattr(waveforms, "get"), + "frame": frames.get, + "waveform": waveforms.get, "expr": FreeParameterExpression, } if argument["type"] in nonprimitive_arg_type.keys(): @@ -360,17 +351,19 @@ def _parse_arg_from_calibration_schema( def _parse_from_calibration_schema( cls, calibration: dict, waveforms: dict[Waveform], frames: dict[Frame] ) -> PulseSequence: - """ - Parsing a JSON input based on https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L26. + """Parsing a JSON input based on https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L26. # noqa: E501 Args: calibration (dict): The pulse instruction to parse waveforms (dict[Waveform]): The waveforms supplied for the pulse sequences. frames (dict[Frame]): A dictionary of frame objects to use. + Raises: + ValueError: If the requested instruction has not been implemented for pulses. + Returns: PulseSequence: The parse sequence obtain from parsing a pulse instruction. - """ # noqa: E501 + """ calibration_sequence = cls() for instr in calibration: if hasattr(PulseSequence, f"{instr['name']}"): @@ -409,18 +402,20 @@ def _parse_from_calibration_schema( raise ValueError(f"The {instr['name']} instruction has not been implemented") return calibration_sequence - def __call__(self, arg: Any | None = None, **kwargs) -> PulseSequence: - """ - Implements the call function to easily make a bound PulseSequence. + def __call__( + self, arg: Any | None = None, **kwargs: Union[FreeParameter, str] + ) -> PulseSequence: + """Implements the call function to easily make a bound PulseSequence. Args: arg (Any | None): A value to bind to all parameters. Defaults to None and can be overridden if the parameter is in kwargs. + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. Returns: PulseSequence: A pulse sequence with the specified parameters bound. """ - param_values = dict() + param_values = {} if arg is not None: for param in self.parameters: param_values[str(param)] = arg @@ -428,7 +423,7 @@ def __call__(self, arg: Any | None = None, **kwargs) -> PulseSequence: param_values[str(key)] = val return self.make_bound_pulse_sequence(param_values) - def __eq__(self, other): + def __eq__(self, other: PulseSequence): sort_input_parameters = True return ( isinstance(other, PulseSequence) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 971321a71..9ca43050a 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -31,11 +31,10 @@ class Waveform(ABC): - """ - A waveform is a time-dependent envelope that can be used to emit signals on an output port + """A waveform is a time-dependent envelope that can be used to emit signals on an output port or receive signals from an input port. As such, when transmitting signals to the qubit, a frame determines time at which the waveform envelope is emitted, its carrier frequency, and - it’s phase offset. When capturing signals from a qubit, at minimum a frame determines the + it's phase offset. When capturing signals from a qubit, at minimum a frame determines the time at which the signal is captured. See https://openqasm.com/language/openpulse.html#waveforms for more details. """ @@ -47,32 +46,35 @@ def _to_oqpy_expression(self) -> OQPyExpression: @abstractmethod def sample(self, dt: float) -> np.ndarray: """Generates a sample of amplitudes for this Waveform based on the given time resolution. + Args: dt (float): The time resolution. + Returns: - ndarray: The sample amplitudes for this waveform. + np.ndarray: The sample amplitudes for this waveform. """ @staticmethod @abstractmethod def _from_calibration_schema(waveform_json: dict) -> Waveform: - """ - Parses a JSON input and returns the BDK waveform. See https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L104 + """Parses a JSON input and returns the BDK waveform. See https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L104 # noqa: E501 Args: waveform_json (dict): A JSON object with the needed parameters for making the Waveform. Returns: Waveform: A Waveform object parsed from the supplied JSON. - """ # noqa: E501 + """ class ArbitraryWaveform(Waveform): """An arbitrary waveform with amplitudes at each timestep explicitly specified using - an array.""" + an array. + """ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): - """ + """Initializes an `ArbitraryWaveform`. + Args: amplitudes (list[complex]): Array of complex values specifying the waveform amplitude at each timestep. The timestep is determined by the sampling rate @@ -86,7 +88,7 @@ def __init__(self, amplitudes: list[complex], id: Optional[str] = None): def __repr__(self) -> str: return f"ArbitraryWaveform('id': {self.id}, 'amplitudes': {self.amplitudes})" - def __eq__(self, other): + def __eq__(self, other: ArbitraryWaveform): return isinstance(other, ArbitraryWaveform) and (self.amplitudes, self.id) == ( other.amplitudes, other.id, @@ -94,6 +96,7 @@ def __eq__(self, other): def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform. + Returns: OQPyExpression: The OQPyExpression. """ @@ -101,10 +104,15 @@ def _to_oqpy_expression(self) -> OQPyExpression: def sample(self, dt: float) -> np.ndarray: """Generates a sample of amplitudes for this Waveform based on the given time resolution. + Args: dt (float): The time resolution. + + Raises: + NotImplementedError: This class does not implement sample. + Returns: - ndarray: The sample amplitudes for this waveform. + np.ndarray: The sample amplitudes for this waveform. """ raise NotImplementedError @@ -117,12 +125,14 @@ def _from_calibration_schema(waveform_json: dict) -> ArbitraryWaveform: class ConstantWaveform(Waveform, Parameterizable): """A constant waveform which holds the supplied `iq` value as its amplitude for the - specified length.""" + specified length. + """ def __init__( self, length: Union[float, FreeParameterExpression], iq: complex, id: Optional[str] = None ): - """ + """Initializes a `ConstantWaveform`. + Args: length (Union[float, FreeParameterExpression]): Value (in seconds) specifying the duration of the waveform. @@ -140,13 +150,20 @@ def __repr__(self) -> str: @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values.""" + expressions or bound values. + + Returns: + list[Union[FreeParameterExpression, FreeParameter, float]]: a list of parameters. + """ return [self.length] - def bind_values(self, **kwargs) -> ConstantWaveform: + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ConstantWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + Returns: ConstantWaveform: A copy of this waveform with the requested parameters bound. """ @@ -157,7 +174,7 @@ def bind_values(self, **kwargs) -> ConstantWaveform: } return ConstantWaveform(**constructor_kwargs) - def __eq__(self, other): + def __eq__(self, other: ConstantWaveform): return isinstance(other, ConstantWaveform) and (self.length, self.iq, self.id) == ( other.length, other.iq, @@ -166,6 +183,7 @@ def __eq__(self, other): def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform. + Returns: OQPyExpression: The OQPyExpression. """ @@ -179,10 +197,12 @@ def _to_oqpy_expression(self) -> OQPyExpression: def sample(self, dt: float) -> np.ndarray: """Generates a sample of amplitudes for this Waveform based on the given time resolution. + Args: dt (float): The time resolution. + Returns: - ndarray: The sample amplitudes for this waveform. + np.ndarray: The sample amplitudes for this waveform. """ # Amplitudes should be gated by [0:self.length] sample_range = np.arange(0, self.length, dt) @@ -221,7 +241,8 @@ def __init__( zero_at_edges: bool = False, id: Optional[str] = None, ): - """ + """Initializes a `DragGaussianWaveform`. + Args: length (Union[float, FreeParameterExpression]): Value (in seconds) specifying the duration of the waveform. @@ -252,13 +273,17 @@ def __repr__(self) -> str: @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values.""" + expressions or bound values. + """ return [self.length, self.sigma, self.beta, self.amplitude] - def bind_values(self, **kwargs) -> DragGaussianWaveform: + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> DragGaussianWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + Returns: DragGaussianWaveform: A copy of this waveform with the requested parameters bound. """ @@ -272,7 +297,7 @@ def bind_values(self, **kwargs) -> DragGaussianWaveform: } return DragGaussianWaveform(**constructor_kwargs) - def __eq__(self, other): + def __eq__(self, other: DragGaussianWaveform): return isinstance(other, DragGaussianWaveform) and ( self.length, self.sigma, @@ -284,6 +309,7 @@ def __eq__(self, other): def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform. + Returns: OQPyExpression: The OQPyExpression. """ @@ -310,10 +336,12 @@ def _to_oqpy_expression(self) -> OQPyExpression: def sample(self, dt: float) -> np.ndarray: """Generates a sample of amplitudes for this Waveform based on the given time resolution. + Args: dt (float): The time resolution. + Returns: - ndarray: The sample amplitudes for this waveform. + np.ndarray: The sample amplitudes for this waveform. """ sample_range = np.arange(0, self.length, dt) t0 = self.length / 2 @@ -354,7 +382,8 @@ def __init__( zero_at_edges: bool = False, id: Optional[str] = None, ): - """ + """Initializes a `GaussianWaveform`. + Args: length (Union[float, FreeParameterExpression]): Value (in seconds) specifying the duration of the waveform. @@ -382,13 +411,17 @@ def __repr__(self) -> str: @property def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: """Returns the parameters associated with the object, either unbound free parameter - expressions or bound values.""" + expressions or bound values. + """ return [self.length, self.sigma, self.amplitude] - def bind_values(self, **kwargs) -> GaussianWaveform: + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> GaussianWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + Returns: GaussianWaveform: A copy of this waveform with the requested parameters bound. """ @@ -401,7 +434,7 @@ def bind_values(self, **kwargs) -> GaussianWaveform: } return GaussianWaveform(**constructor_kwargs) - def __eq__(self, other): + def __eq__(self, other: GaussianWaveform): return isinstance(other, GaussianWaveform) and ( self.length, self.sigma, @@ -412,6 +445,7 @@ def __eq__(self, other): def _to_oqpy_expression(self) -> OQPyExpression: """Returns an OQPyExpression defining this waveform. + Returns: OQPyExpression: The OQPyExpression. """ @@ -436,10 +470,12 @@ def _to_oqpy_expression(self) -> OQPyExpression: def sample(self, dt: float) -> np.ndarray: """Generates a sample of amplitudes for this Waveform based on the given time resolution. + Args: dt (float): The time resolution. + Returns: - ndarray: The sample amplitudes for this waveform. + np.ndarray: The sample amplitudes for this waveform. """ sample_range = np.arange(0, self.length, dt) t0 = self.length / 2 @@ -466,7 +502,7 @@ def _from_calibration_schema(waveform_json: dict) -> GaussianWaveform: def _make_identifier_name() -> str: - return "".join([random.choice(string.ascii_letters) for _ in range(10)]) + return "".join([random.choice(string.ascii_letters) for _ in range(10)]) # noqa S311 def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 1e4624e01..9064b942a 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -35,17 +35,19 @@ class PauliString: - """ - A lightweight representation of a Pauli string with its phase. - """ + """A lightweight representation of a Pauli string with its phase.""" def __init__(self, pauli_string: Union[str, PauliString]): - """ + """Initializes a `PauliString`. + Args: pauli_string (Union[str, PauliString]): The representation of the pauli word, either a string or another PauliString object. A valid string consists of an optional phase, specified by an optional sign +/- followed by an uppercase string in {I, X, Y, Z}. Example valid strings are: XYZ, +YIZY, -YX + + Raises: + ValueError: If the Pauli String is empty. """ if not pauli_string: raise ValueError("pauli_string must not be empty") @@ -343,7 +345,7 @@ def to_circuit(self) -> Circuit: circ = circ.z(qubit) return circ - def __eq__(self, other): + def __eq__(self, other: PauliString): if isinstance(other, PauliString): return ( self._phase == other._phase @@ -352,7 +354,7 @@ def __eq__(self, other): ) return False - def __getitem__(self, item): + def __getitem__(self, item: int): if item >= self._qubit_count: raise IndexError(item) return _PAULI_INDICES[self._nontrivial.get(item, "I")] diff --git a/src/braket/registers/qubit.py b/src/braket/registers/qubit.py index 479a453e3..4be98b640 100644 --- a/src/braket/registers/qubit.py +++ b/src/braket/registers/qubit.py @@ -20,19 +20,22 @@ class Qubit(int): - """ - A quantum bit index. The index of this qubit is locally scoped towards the contained + """A quantum bit index. The index of this qubit is locally scoped towards the contained circuit. This may not be the exact qubit index on the quantum device. """ - def __new__(cls, index: int): - """ + def __new__(cls, index: int) -> Qubit: + """Creates a new `Qubit`. + Args: index (int): Index of the qubit. Raises: ValueError: If `index` is less than zero. + Returns: + Qubit: Returns a new Qubit object. + Examples: >>> Qubit(0) >>> Qubit(1) @@ -51,8 +54,7 @@ def __str__(self): @staticmethod def new(qubit: QubitInput) -> Qubit: - """ - Helper constructor - if input is a `Qubit` it returns the same value, + """Helper constructor - if input is a `Qubit` it returns the same value, else a new `Qubit` is constructed. Args: @@ -61,7 +63,6 @@ def new(qubit: QubitInput) -> Qubit: Returns: Qubit: The qubit. """ - if isinstance(qubit, Qubit): return qubit else: diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py index 0f9d0a7ac..e7bed6aa8 100644 --- a/src/braket/registers/qubit_set.py +++ b/src/braket/registers/qubit_set.py @@ -32,8 +32,7 @@ def _flatten(other: Any) -> Any: class QubitSet(IndexedSet): - """ - An ordered, unique set of quantum bits. + """An ordered, unique set of quantum bits. Note: QubitSet implements `__hash__()` but is a mutable object, therefore be careful when @@ -41,7 +40,8 @@ class QubitSet(IndexedSet): """ def __init__(self, qubits: QubitSetInput | None = None): - """ + """Initializes a `QubitSet`. + Args: qubits (QubitSetInput | None): Qubits to be included in the `QubitSet`. Default is `None`. @@ -63,13 +63,11 @@ def __init__(self, qubits: QubitSetInput | None = None): Qubit(2) Qubit(3) """ - _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] if qubits is not None else None super().__init__(_qubits) def map(self, mapping: dict[QubitInput, QubitInput]) -> QubitSet: - """ - Creates a new `QubitSet` where this instance's qubits are mapped to the values in `mapping`. + """Creates a new `QubitSet` where this instance's qubits are mapped to the values in `mapping`. If this instance contains a qubit that is not in the `mapping` that qubit is not modified. Args: @@ -85,8 +83,7 @@ def map(self, mapping: dict[QubitInput, QubitInput]) -> QubitSet: >>> mapping = {0: 10, Qubit(1): Qubit(11)} >>> qubits.map(mapping) QubitSet([Qubit(10), Qubit(11)]) - """ - + """ # noqa E501 new_qubits = [mapping.get(qubit, qubit) for qubit in self] return QubitSet(new_qubits) diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index bb6cc6e77..d40b0547c 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import braket.ipython_utils as ipython_utils +from braket import ipython_utils from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( # noqa: F401 AnalogHamiltonianSimulationQuantumTaskResult, AnalogHamiltonianSimulationShotStatus, diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index abc39753d..ade8a0f79 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -38,7 +38,7 @@ class ShotResult: pre_sequence: np.ndarray = None post_sequence: np.ndarray = None - def __eq__(self, other) -> bool: + def __eq__(self, other: ShotResult) -> bool: if isinstance(other, ShotResult): return ( self.status == other.status @@ -54,7 +54,7 @@ class AnalogHamiltonianSimulationQuantumTaskResult: additional_metadata: AdditionalMetadata measurements: list[ShotResult] = None - def __eq__(self, other) -> bool: + def __eq__(self, other: AnalogHamiltonianSimulationQuantumTaskResult) -> bool: if isinstance(other, AnalogHamiltonianSimulationQuantumTaskResult): return ( self.task_metadata.id == other.task_metadata.id @@ -118,7 +118,6 @@ def get_counts(self) -> dict[str, int]: Returns None if none of shot measurements are successful. Only succesful shots contribute to the state count. """ - state_counts = Counter() states = ["e", "r", "g"] for shot in self.measurements: @@ -129,7 +128,7 @@ def get_counts(self) -> dict[str, int]: state_idx = [ 0 if pre_i == 0 else 1 if post_i == 0 else 2 for pre_i, post_i in zip(pre, post) ] - state = "".join(map(lambda s_idx: states[s_idx], state_idx)) + state = "".join(states[s_idx] for s_idx in state_idx) state_counts.update((state,)) return dict(state_counts) @@ -138,9 +137,8 @@ def get_avg_density(self) -> np.ndarray: """Get the average Rydberg state densities from the result Returns: - ndarray: The average densities from the result + np.ndarray: The average densities from the result """ - counts = self.get_counts() N_ryd, N_ground = [], [] diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 1a5fbc2cb..bf5e9900d 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -13,10 +13,11 @@ from __future__ import annotations +from collections.abc import Generator from dataclasses import dataclass -from typing import List, Optional, Tuple +from typing import Optional -import numpy +import numpy as np from braket.annealing import ProblemType from braket.task_result import AdditionalMetadata, AnnealingTaskResult, TaskMetadata @@ -24,12 +25,11 @@ @dataclass class AnnealingQuantumTaskResult: - """ - Result of an annealing problem quantum task execution. This class is intended + """Result of an annealing problem quantum task execution. This class is intended to be initialized by a QuantumTask class. Args: - record_array (numpy.recarray): numpy array with keys 'solution' (numpy.ndarray) + record_array (np.recarray): numpy array with keys 'solution' (np.ndarray) where row is solution, column is value of the variable, 'solution_count' (numpy.ndarray) the number of times the solutions occurred, and 'value' (numpy.ndarray) the output or energy of the solutions. @@ -39,7 +39,7 @@ class AnnealingQuantumTaskResult: additional_metadata (AdditionalMetadata): Additional metadata about the quantum task """ - record_array: numpy.recarray + record_array: np.recarray variable_count: int problem_type: ProblemType task_metadata: TaskMetadata @@ -47,38 +47,37 @@ class AnnealingQuantumTaskResult: def data( self, - selected_fields: Optional[List[str]] = None, + selected_fields: Optional[list[str]] = None, sorted_by: str = "value", reverse: bool = False, - ) -> Tuple: - """ - Iterate over the data in record_array + ) -> Generator[tuple]: + """Yields the data in record_array Args: - selected_fields (Optional[List[str]]): selected fields to return. + selected_fields (Optional[list[str]]): selected fields to return. Options are 'solution', 'value', and 'solution_count'. Default is None. sorted_by (str): Sorts the data by this field. Options are 'solution', 'value', and 'solution_count'. Default is 'value'. reverse (bool): If True, returns the data in reverse order. Default is False. Yields: - Tuple: data in record_array + Generator[tuple]: data in record_array """ if selected_fields is None: selected_fields = ["solution", "value", "solution_count"] if sorted_by is None: - order = numpy.arange(len(self.record_array)) + order = np.arange(len(self.record_array)) else: - order = numpy.argsort(self.record_array[sorted_by]) + order = np.argsort(self.record_array[sorted_by]) if reverse: - order = numpy.flip(order) + order = np.flip(order) for i in order: yield tuple(self.record_array[field][i] for field in selected_fields) - def __eq__(self, other) -> bool: + def __eq__(self, other: AnnealingQuantumTaskResult) -> bool: if isinstance(other, AnnealingQuantumTaskResult): # __eq__ on numpy arrays results in an array of booleans and therefore can't use # the default dataclass __eq__ implementation. Override equals to check if all @@ -100,8 +99,7 @@ def __eq__(self, other) -> bool: @staticmethod def from_object(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: - """ - Create AnnealingQuantumTaskResult from AnnealingTaskResult object + """Create AnnealingQuantumTaskResult from AnnealingTaskResult object Args: result (AnnealingTaskResult): AnnealingTaskResult object @@ -114,8 +112,7 @@ def from_object(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: @staticmethod def from_string(result: str) -> AnnealingQuantumTaskResult: - """ - Create AnnealingQuantumTaskResult from string + """Create AnnealingQuantumTaskResult from string Args: result (str): JSON object string @@ -127,12 +124,12 @@ def from_string(result: str) -> AnnealingQuantumTaskResult: @classmethod def _from_object(cls, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: - solutions = numpy.asarray(result.solutions, dtype=int) - values = numpy.asarray(result.values, dtype=float) + solutions = np.asarray(result.solutions, dtype=int) + values = np.asarray(result.values, dtype=float) if not result.solutionCounts: - solution_counts = numpy.ones(len(solutions), dtype=int) + solution_counts = np.ones(len(solutions), dtype=int) else: - solution_counts = numpy.asarray(result.solutionCounts, dtype=int) + solution_counts = np.asarray(result.solutionCounts, dtype=int) record_array = AnnealingQuantumTaskResult._create_record_array( solutions, solution_counts, values ) @@ -150,15 +147,17 @@ def _from_object(cls, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult @staticmethod def _create_record_array( - solutions: numpy.ndarray, solution_counts: numpy.ndarray, values: numpy.ndarray - ) -> numpy.recarray: - """ - Create a solutions record for AnnealingQuantumTaskResult + solutions: np.ndarray, solution_counts: np.ndarray, values: np.ndarray + ) -> np.recarray: + """Create a solutions record for AnnealingQuantumTaskResult Args: - solutions (numpy.ndarray): row is solution, column is value of the variable - solution_counts (numpy.ndarray): list of number of times the solutions occurred - values (numpy.ndarray): list of the output or energy of the solutions + solutions (np.ndarray): row is solution, column is value of the variable + solution_counts (np.ndarray): list of number of times the solutions occurred + values (np.ndarray): list of the output or energy of the solutions + + Returns: + np.recarray: A record array for solutions, value, and solution_count. """ num_solutions, variable_count = solutions.shape datatypes = [ @@ -167,7 +166,7 @@ def _create_record_array( ("solution_count", solution_counts.dtype), ] - record = numpy.rec.array(numpy.zeros(num_solutions, dtype=datatypes)) + record = np.rec.array(np.zeros(num_solutions, dtype=datatypes)) record["solution"] = solutions record["value"] = values record["solution_count"] = solution_counts diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 631ca45e7..dec914ba7 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -36,8 +36,7 @@ @dataclass class GateModelQuantumTaskResult: - """ - Result of a gate model quantum task execution. This class is intended + """Result of a gate model quantum task execution. This class is intended to be initialized by a QuantumTask class. Args: @@ -96,16 +95,15 @@ class GateModelQuantumTaskResult: def __post_init__(self): if self.result_types is not None: - self._result_types_indices = dict( - (GateModelQuantumTaskResult._result_type_hash(rt.type), i) + self._result_types_indices = { + GateModelQuantumTaskResult._result_type_hash(rt.type): i for i, rt in enumerate(self.result_types) - ) + } else: self._result_types_indices = {} def get_value_by_result_type(self, result_type: ResultType) -> Any: - """ - Get value by result type. The result type must have already been + """Get value by result type. The result type must have already been requested in the circuit sent to the device for this quantum task result. Args: @@ -126,17 +124,16 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: except KeyError: raise ValueError( "Result type not found in result. " - + "Result types must be added to circuit before circuit is run on device." + "Result types must be added to circuit before circuit is run on device." ) - def __eq__(self, other) -> bool: + def __eq__(self, other: GateModelQuantumTaskResult) -> bool: if isinstance(other, GateModelQuantumTaskResult): return self.task_metadata.id == other.task_metadata.id return NotImplemented def get_compiled_circuit(self) -> Optional[str]: - """ - Get the compiled circuit, if one is available. + """Get the compiled circuit, if one is available. Returns: Optional[str]: The compiled circuit or None. @@ -153,11 +150,10 @@ def get_compiled_circuit(self) -> Optional[str]: @staticmethod def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: - """ - Creates measurement counts from measurements + """Creates measurement counts from measurements Args: - measurements (ndarray): 2d array - row is shot and column is qubit. + measurements (np.ndarray): 2d array - row is shot and column is qubit. Returns: Counter: A Counter of measurements. Key is the measurements in a big endian binary @@ -172,8 +168,7 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: def measurement_probabilities_from_measurement_counts( measurement_counts: Counter, ) -> dict[str, float]: - """ - Creates measurement probabilities from measurement counts + """Creates measurement probabilities from measurement counts Args: measurement_counts (Counter): A Counter of measurements. Key is the measurements @@ -195,8 +190,7 @@ def measurement_probabilities_from_measurement_counts( def measurements_from_measurement_probabilities( measurement_probabilities: dict[str, float], shots: int ) -> np.ndarray: - """ - Creates measurements from measurement probabilities. + """Creates measurements from measurement probabilities. Args: measurement_probabilities (dict[str, float]): A dictionary of probabilistic results. @@ -205,7 +199,7 @@ def measurements_from_measurement_probabilities( shots (int): number of iterations on device. Returns: - ndarray: A dictionary of probabilistic results. + np.ndarray: A dictionary of probabilistic results. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. """ @@ -220,8 +214,7 @@ def measurements_from_measurement_probabilities( @staticmethod def from_object(result: GateModelTaskResult) -> GateModelQuantumTaskResult: - """ - Create GateModelQuantumTaskResult from GateModelTaskResult object. + """Create GateModelQuantumTaskResult from GateModelTaskResult object. Args: result (GateModelTaskResult): GateModelTaskResult object @@ -237,8 +230,7 @@ def from_object(result: GateModelTaskResult) -> GateModelQuantumTaskResult: @staticmethod def from_string(result: str) -> GateModelQuantumTaskResult: - """ - Create GateModelQuantumTaskResult from string. + """Create GateModelQuantumTaskResult from string. Args: result (str): JSON object string, with GateModelQuantumTaskResult attributes as keys. @@ -350,8 +342,7 @@ def _from_dict_internal_simulator_only( @staticmethod def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: - """ - Casts the result types to the types expected by the SDK. + """Casts the result types to the types expected by the SDK. Args: gate_model_task_result (GateModelTaskResult): GateModelTaskResult representing the diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 69a0f1bd6..8417c71b2 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -39,6 +39,11 @@ def __init__( @property def id(self) -> str: + """Gets the task ID. + + Returns: + str: The ID of the task. + """ return str(self._id) def cancel(self) -> None: @@ -46,6 +51,11 @@ def cancel(self) -> None: raise NotImplementedError("Cannot cancel completed local task") def state(self) -> str: + """Gets the state of the task. + + Returns: + str: Returns COMPLETED + """ return "COMPLETED" def result( @@ -57,8 +67,12 @@ def result( def async_result(self) -> asyncio.Task: """Get the quantum task result asynchronously. + + Raises: + NotImplementedError: Asynchronous local simulation unsupported + Returns: - Task: Get the quantum task result asynchronously. + asyncio.Task: Get the quantum task result asynchronously. """ # TODO: Allow for asynchronous simulation raise NotImplementedError("Asynchronous local simulation unsupported") diff --git a/src/braket/tasks/photonic_model_quantum_task_result.py b/src/braket/tasks/photonic_model_quantum_task_result.py index 4d2fa0201..59eba68ac 100644 --- a/src/braket/tasks/photonic_model_quantum_task_result.py +++ b/src/braket/tasks/photonic_model_quantum_task_result.py @@ -26,15 +26,14 @@ class PhotonicModelQuantumTaskResult: additional_metadata: AdditionalMetadata measurements: np.ndarray = None - def __eq__(self, other) -> bool: + def __eq__(self, other: PhotonicModelQuantumTaskResult) -> bool: if isinstance(other, PhotonicModelQuantumTaskResult): return self.task_metadata.id == other.task_metadata.id return NotImplemented @staticmethod def from_object(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: - """ - Create PhotonicModelQuantumTaskResult from PhotonicModelTaskResult object. + """Create PhotonicModelQuantumTaskResult from PhotonicModelTaskResult object. Args: result (PhotonicModelTaskResult): PhotonicModelTaskResult object diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index c306078ca..f07a1be7b 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -27,6 +27,7 @@ class QuantumTask(ABC): @abstractmethod def id(self) -> str: """Get the quantum task ID. + Returns: str: The quantum task ID. """ @@ -38,6 +39,7 @@ def cancel(self) -> None: @abstractmethod def state(self) -> str: """Get the state of the quantum task. + Returns: str: State of the quantum task. """ @@ -49,22 +51,23 @@ def result( GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult ]: """Get the quantum task result. + Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: # noqa - Get the quantum task result. Call async_result if you want the result in an + Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: Get + the quantum task result. Call async_result if you want the result in an asynchronous way. - """ + """ # noqa E501 @abstractmethod def async_result(self) -> asyncio.Task: """Get the quantum task result asynchronously. + Returns: - Task: Get the quantum task result asynchronously. + asyncio.Task: Get the quantum task result asynchronously. """ - def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: - """ - Get task metadata. + def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: # noqa B027 + """Get task metadata. Args: use_cached_value (bool): If True, uses the value retrieved from the previous diff --git a/src/braket/tasks/quantum_task_batch.py b/src/braket/tasks/quantum_task_batch.py index ff0a0b82a..790f6e19a 100644 --- a/src/braket/tasks/quantum_task_batch.py +++ b/src/braket/tasks/quantum_task_batch.py @@ -31,7 +31,8 @@ def results( ] ]: """Get the quantum task results. + Returns: - list[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]:: # noqa - Get the quantum task results. - """ + list[Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]]: Get + the quantum task results. + """ # noqa: E501 diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 9d2b9cf00..a558bec71 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -19,7 +19,6 @@ from decimal import Decimal from enum import Enum from numbers import Number -from typing import Union @dataclass @@ -105,6 +104,9 @@ def from_lists(times: list[float], values: list[float]) -> TimeSeries: Returns: TimeSeries: time series constructed from lists + + Raises: + ValueError: If the len of `times` does not equal len of `values`. """ if len(times) != len(values): raise ValueError( @@ -118,12 +120,12 @@ def from_lists(times: list[float], values: list[float]) -> TimeSeries: return ts @staticmethod - def constant_like(times: Union[list[float], TimeSeries], constant: float = 0.0) -> TimeSeries: + def constant_like(times: list | float | TimeSeries, constant: float = 0.0) -> TimeSeries: """Obtain a constant time series given another time series or the list of time points, - and the constant values + and the constant values. Args: - times (Union[list[float], TimeSeries]): list of time points or a time series + times (list | float | TimeSeries): list of time points or a time series constant (float): constant value Returns: @@ -154,6 +156,10 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: Returns: TimeSeries: The concatenated time series. + Raises: + ValueError: If the timeseries is not empty and time points in the first + TimeSeries are not strictly smaller than in the second. + Example: :: time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) @@ -165,7 +171,6 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: concat_ts.times() = [0, 0.1, 0.2, 0.3] concat_ts.values() = [1, 2, 4, 5] """ - not_empty_ts = len(other.times()) * len(self.times()) != 0 if not_empty_ts and min(other.times()) <= max(self.times()): raise ValueError( @@ -202,6 +207,9 @@ def stitch( Returns: TimeSeries: The stitched time series. + Raises: + ValueError: If boundary is not one of {"mean", "left", "right"}. + Example (StitchBoundaryCondition.MEAN): :: time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) @@ -229,7 +237,6 @@ def stitch( stitch_ts.times() = [0, 0.1, 0.3] stitch_ts.values() = [1, 4, 5] """ - if len(self.times()) == 0: return TimeSeries.from_lists(times=other.times(), values=other.values()) if len(other.times()) == 0: @@ -287,18 +294,20 @@ def periodic_signal(times: list[float], values: list[float], num_repeat: int = 1 Args: times (list[float]): List of time points in a single block values (list[float]): Values for the time series in a single block - num_repeat (int): Number of block repeatitions + num_repeat (int): Number of block repetitions + + Raises: + ValueError: If the first and last values are not the same Returns: TimeSeries: A new periodic time series. """ - if not (values[0] == values[-1]): - raise ValueError("The first and last values must coinscide to guarantee periodicity") + raise ValueError("The first and last values must coincide to guarantee periodicity") new_time_series = TimeSeries() repeating_block = TimeSeries.from_lists(times=times, values=values) - for index in range(num_repeat): + for _index in range(num_repeat): new_time_series = new_time_series.stitch(repeating_block) return new_time_series @@ -316,6 +325,9 @@ def trapezoidal_signal( slew_rate_max (float): The maximum slew rate time_separation_min (float): The minimum separation of time points + Raises: + ValueError: If the time separation is negative + Returns: TimeSeries: A trapezoidal time series @@ -324,7 +336,6 @@ def trapezoidal_signal( f(t) from t=0 to t=T, where T is the duration. We also assume the trapezoidal time series starts and ends at zero. """ - if area <= 0.0: raise ValueError("The area of the trapezoidal time series has to be positive.") if value_max <= 0.0: @@ -370,8 +381,7 @@ def trapezoidal_signal( # TODO: Verify if this belongs here. def _all_close(first: TimeSeries, second: TimeSeries, tolerance: Number = 1e-7) -> bool: - """ - Returns True if the times and values in two time series are all within (less than) + """Returns True if the times and values in two time series are all within (less than) a given tolerance range. The values in the TimeSeries must be numbers that can be subtracted from each-other, support getting the absolute value, and can be compared against the tolerance. diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py index 2b049b251..c269208c2 100644 --- a/src/braket/tracking/pricing.py +++ b/src/braket/tracking/pricing.py @@ -53,9 +53,13 @@ def get_prices(self) -> None: text_response.readline() self._price_list = list(csv.DictReader(text_response)) - @lru_cache() - def price_search(self, **kwargs) -> list[dict[str, str]]: + @lru_cache + def price_search(self, **kwargs: str) -> list[dict[str, str]]: """Searches the price list for a given set of parameters. + + Args: + **kwargs (str): Arbitrary keyword arguments. + Returns: list[dict[str, str]]: The price list. """ diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 84b294ad2..2f77eec66 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -30,8 +30,7 @@ class Tracker: - """ - Amazon Braket cost tracker. + """Amazon Braket cost tracker. Use this class to track costs incurred from quantum tasks on Amazon Braket. """ @@ -47,6 +46,7 @@ def __exit__(self, *args): def start(self) -> Tracker: """Start tracking resources with this tracker. + Returns: Tracker: self. """ @@ -54,6 +54,7 @@ def start(self) -> Tracker: def stop(self) -> Tracker: """Stop tracking resources with this tracker. + Returns: Tracker: self. """ @@ -61,14 +62,14 @@ def stop(self) -> Tracker: def receive_event(self, event: _TaskCreationEvent) -> None: """Process a Tack Creation Event. + Args: event (_TaskCreationEvent): The event to process. """ self._recieve_internal(event) def tracked_resources(self) -> list[str]: - """ - Resources tracked by this tracker. + """Resources tracked by this tracker. Returns: list[str]: The list of quantum task ids for quantum tasks tracked by this tracker. @@ -76,8 +77,7 @@ def tracked_resources(self) -> list[str]: return list(self._resources.keys()) def qpu_tasks_cost(self) -> Decimal: - """ - Estimate cost of all quantum tasks tracked by this tracker that use Braket qpu devices. + """Estimate cost of all quantum tasks tracked by this tracker that use Braket qpu devices. Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual @@ -95,8 +95,8 @@ def qpu_tasks_cost(self) -> Decimal: return total_cost def simulator_tasks_cost(self) -> Decimal: - """ - Estimate cost of all quantum tasks tracked by this tracker using Braket simulator devices. + """Estimate cost of all quantum tasks tracked by this tracker using Braket simulator + devices. Note: The cost of a simulator quantum task is not available until after the results for the task have been fetched. Call `result()` on an `AwsQuantumTask` before estimating its cost @@ -118,11 +118,10 @@ def simulator_tasks_cost(self) -> Decimal: return total_cost def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: - """ - Get a summary of quantum tasks grouped by device. + """Get a summary of quantum tasks grouped by device. Returns: - dict[str,dict[str,Any]] : A dictionary where each key is a device arn, and maps to + dict[str, dict[str, Any]]: A dictionary where each key is a device arn, and maps to a dictionary sumarizing the quantum tasks run on the device. The summary includes the total shots sent to the device and the most recent status of the quantum tasks created on this device. For finished quantum tasks on simulator devices, the summary diff --git a/src/braket/tracking/tracking_context.py b/src/braket/tracking/tracking_context.py index 128af025d..37f4a3dc0 100644 --- a/src/braket/tracking/tracking_context.py +++ b/src/braket/tracking/tracking_context.py @@ -20,6 +20,7 @@ def __init__(self): def register_tracker(self, tracker: Tracker) -> None: # noqa F821 """Registers a tracker. + Args: tracker (Tracker): The tracker. """ @@ -27,6 +28,7 @@ def register_tracker(self, tracker: Tracker) -> None: # noqa F821 def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 """Deregisters a tracker. + Args: tracker (Tracker): The tracker. """ @@ -34,13 +36,19 @@ def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 def broadcast_event(self, event: _TrackingEvent) -> None: # noqa F821 """Broadcasts an event to all trackers. + Args: event (_TrackingEvent): The event to broadcast. """ for tracker in self._trackers: tracker.receive_event(event) - def active_trackers(self) -> None: + def active_trackers(self) -> set: + """Gets the active trackers. + + Returns: + set: The set of active trackers. + """ return self._trackers diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 6ccd6f05b..63b03bfd6 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -139,7 +139,7 @@ def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: assert np.allclose( result.get_value_by_result_type(ResultType.Probability()), np.array([0.5, 0, 0, 0.5]), - **tol + **tol, ) @@ -154,7 +154,7 @@ def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwar assert np.allclose( result.get_value_by_result_type(ResultType.Probability(target=0)), np.array([0.5, 0.5]), - **tol + **tol, ) @@ -592,7 +592,7 @@ def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: Dict assert np.allclose( result.get_value_by_result_type(ResultType.Probability()), np.array([0.0, 0.1, 0, 0.9]), - **tol + **tol, ) @@ -611,7 +611,7 @@ def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict assert np.allclose( result.get_value_by_result_type(ResultType.Probability()), np.array([0.1, 0.0, 0, 0.9]), - **tol + **tol, ) @@ -671,7 +671,7 @@ def openqasm_noisy_circuit_1qubit_noise_full_probability( assert np.allclose( result.get_value_by_result_type(ResultType.Probability(target=[0, 1])), np.array([0.0, 0.1, 0, 0.9]), - **tol + **tol, ) diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 2b55dfa4f..a9bd3f5a3 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -225,7 +225,7 @@ def multi_probability_invalid_input(**kwargs): "TwoDimensionalMatrixList": two_dimensional_matrix_list_valid_input, "DoubleTarget": double_target_valid_input, "DoubleControl": double_control_valid_input, - } + }, ) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index a2203ee60..4877a0b8f 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -118,7 +118,7 @@ def run( shots: Optional[int], inputs: Optional[Dict[str, float]], *args, - **kwargs + **kwargs, ) -> Dict[str, Any]: self._shots = shots self._qubits = qubits From 0d23ac1cecb9e2adde97c3181af31103b7754b03 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 19 Feb 2024 12:27:21 -0800 Subject: [PATCH 060/347] infra: reuse AWS calls across integ tests (#797) --- .gitignore | 1 + test/integ_tests/conftest.py | 88 ++++++++++++ .../gate_model_device_testing_utils.py | 10 +- test/integ_tests/job_test_script.py | 4 +- .../test_create_local_quantum_job.py | 1 + test/integ_tests/test_create_quantum_job.py | 47 +++---- .../test_density_matrix_simulator.py | 2 +- test/integ_tests/test_device_creation.py | 33 +++-- test/integ_tests/test_queue_information.py | 17 +-- .../test_simulator_quantum_task.py | 126 +++++++++++------- .../test_tensor_network_simulator.py | 2 +- tox.ini | 7 + 12 files changed, 223 insertions(+), 115 deletions(-) diff --git a/.gitignore b/.gitignore index d91f4d305..bca0430b0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ __pycache__/ /build /venv /dist +/model.tar.gz diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 3c9edd2f8..656353c67 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -12,13 +12,62 @@ # language governing permissions and limitations under the License. import os +import random +import string import boto3 import pytest from botocore.exceptions import ClientError +from braket.aws.aws_device import AwsDevice +from braket.aws.aws_quantum_job import AwsQuantumJob from braket.aws.aws_session import AwsSession +SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" +TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" +SIMULATOR_ARNS = [SV1_ARN, DM1_ARN, TN1_ARN] + +job_complete_name = "".join(random.choices(string.ascii_lowercase + string.digits, k=12)) +job_fail_name = "".join(random.choices(string.ascii_lowercase + string.digits, k=12)) + + +def pytest_configure_node(node): + """xdist hook""" + node.workerinput["JOB_COMPLETED_NAME"] = job_complete_name + node.workerinput["JOB_FAILED_NAME"] = job_fail_name + + +def pytest_xdist_node_collection_finished(ids): + """Uses the pytest xdist hook to check whether tests with jobs are to be ran. + If they are, the first reporting worker sets a flag that it created the tests + to avoid concurrency limits. This is the first time in the pytest setup the + controller has all the tests to be ran from the worker nodes. + """ + run_jobs = any("job" in test for test in ids) + profile_name = os.environ["AWS_PROFILE"] + aws_session = AwsSession(boto3.session.Session(profile_name=profile_name)) + if run_jobs and os.getenv("JOBS_STARTED") is None: + AwsQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + job_name=job_fail_name, + source_module="test/integ_tests/job_test_script.py", + entry_point="job_test_script:start_here", + aws_session=aws_session, + wait_until_complete=False, + hyperparameters={"test_case": "failed"}, + ) + AwsQuantumJob.create( + "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + job_name=job_complete_name, + source_module="test/integ_tests/job_test_script.py", + entry_point="job_test_script:start_here", + aws_session=aws_session, + wait_until_complete=False, + hyperparameters={"test_case": "completed"}, + ) + os.environ["JOBS_STARTED"] = "True" + @pytest.fixture(scope="session") def boto_session(): @@ -82,3 +131,42 @@ def s3_prefix(): @pytest.fixture(scope="module") def s3_destination_folder(s3_bucket, s3_prefix): return AwsSession.S3DestinationFolder(s3_bucket, s3_prefix) + + +@pytest.fixture(scope="session") +def braket_simulators(aws_session): + return { + simulator_arn: AwsDevice(simulator_arn, aws_session) for simulator_arn in SIMULATOR_ARNS + } + + +@pytest.fixture(scope="session") +def braket_devices(): + return AwsDevice.get_devices(statuses=["RETIRED", "ONLINE", "OFFLINE"]) + + +@pytest.fixture(scope="session", autouse=True) +def created_braket_devices(aws_session, braket_devices): + return {device.arn: device for device in braket_devices} + + +@pytest.fixture(scope="session") +def job_completed_name(request): + return request.config.workerinput["JOB_COMPLETED_NAME"] + + +@pytest.fixture(scope="session") +def job_failed_name(request): + return request.config.workerinput["JOB_FAILED_NAME"] + + +@pytest.fixture(scope="session", autouse=True) +def completed_quantum_job(aws_session, job_completed_name): + job = AwsQuantumJob(arn=f"arn:aws:braket:us-west-2:046073650652:job/{job_completed_name}") + return job + + +@pytest.fixture(scope="session", autouse=True) +def failed_quantum_job(aws_session, job_failed_name): + job = AwsQuantumJob(arn=f"arn:aws:braket:us-west-2:046073650652:job/{job_failed_name}") + return job diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 63b03bfd6..191084145 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -81,8 +81,8 @@ def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict .variance(observable=Observable.Y(), target=[3]) ) bell_qasm = bell.to_ir(ir_type=IRType.OPENQASM) - for task in (bell, bell_qasm): - result = device.run(task, **run_kwargs).result() + results = device.run_batch([bell, bell_qasm], **run_kwargs).results() + for result in results: assert np.allclose(result.values[0], 0, **tol) assert np.allclose(result.values[1], 1, **tol) @@ -103,9 +103,9 @@ def result_types_zero_shots_bell_pair_testing( circuit.amplitude(["01", "10", "00", "11"]) if include_state_vector: circuit.state_vector() - tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) - for task in tasks: - result = device.run(task, **run_kwargs).result() + tasks = [circuit, circuit.to_ir(ir_type=IRType.OPENQASM)] + results = device.run_batch(tasks, **run_kwargs).results() + for result in results: assert len(result.result_types) == 3 if include_state_vector else 2 assert np.allclose( result.get_value_by_result_type( diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py index 95b890d60..42303465e 100644 --- a/test/integ_tests/job_test_script.py +++ b/test/integ_tests/job_test_script.py @@ -43,8 +43,8 @@ def completed_job_script(): device = AwsDevice(get_job_device_arn()) bell = Circuit().h(0).cnot(0, 1) - for count in range(5): - task = device.run(bell, shots=100) + for count in range(3): + task = device.run(bell, shots=10) print(task.result().measurement_counts) save_job_result({"converged": True, "energy": -0.2}) save_job_checkpoint({"some_data": "abc"}, checkpoint_file_suffix="plain_data") diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index ad91d0a03..8ceae35cd 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -99,6 +99,7 @@ def test_completed_local_job(aws_session, capsys): for data in logs_to_validate: assert data in log_data + finally: os.chdir(current_dir) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 02c16313b..8b2eae758 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -16,6 +16,7 @@ import re import sys import tempfile +import time from pathlib import Path import job_test_script @@ -36,27 +37,24 @@ def decorator_python_version(): return int(major_version), int(minor_version) -def test_failed_quantum_job(aws_session, capsys): +def test_failed_quantum_job(aws_session, capsys, failed_quantum_job): """Asserts the hybrid job is failed with the output, checkpoints, quantum tasks not created in bucket and only input is uploaded to s3. Validate the results/download results have the response raising RuntimeError. Also, check if the logs displays the Assertion Error. """ - - job = AwsQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module="test/integ_tests/job_test_script.py", - entry_point="job_test_script:start_here", - aws_session=aws_session, - wait_until_complete=True, - hyperparameters={"test_case": "failed"}, - ) + job = failed_quantum_job + job_name = job.name pattern = f"^arn:aws:braket:{aws_session.region}:\\d{{12}}:job/[a-z0-9-]+$" assert re.match(pattern=pattern, string=job.arn) # Check job is in failed state. - assert job.state() == "FAILED" + while True: + time.sleep(5) + if job.state() in AwsQuantumJob.TERMINAL_STATES: + break + assert job.state(use_cached_value=True) == "FAILED" # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. @@ -97,27 +95,22 @@ def test_failed_quantum_job(aws_session, capsys): ) -def test_completed_quantum_job(aws_session, capsys): +def test_completed_quantum_job(aws_session, capsys, completed_quantum_job): """Asserts the hybrid job is completed with the output, checkpoints, quantum tasks and script folder created in S3 for respective hybrid job. Validate the results are downloaded and results are what we expect. Also, assert that logs contains all the necessary steps for setup and running the hybrid job and is displayed to the user. """ - job = AwsQuantumJob.create( - "arn:aws:braket:::device/quantum-simulator/amazon/sv1", - source_module="test/integ_tests/job_test_script.py", - entry_point="job_test_script:start_here", - wait_until_complete=True, - aws_session=aws_session, - hyperparameters={"test_case": "completed"}, - ) - + job = completed_quantum_job + job_name = job.name pattern = f"^arn:aws:braket:{aws_session.region}:\\d{{12}}:job/[a-z0-9-]+$" assert re.match(pattern=pattern, string=job.arn) - # check job is in completed state. - assert job.state() == "COMPLETED" + # Check the job has completed + job.result() + + assert job.state(use_cached_value=True) == "COMPLETED" # Check whether the respective folder with files are created for script, # output, tasks and checkpoints. @@ -179,19 +172,11 @@ def test_completed_quantum_job(aws_session, capsys): == expected_data ) - # Check downloaded results exists in the file system after the call. - downloaded_result = f"{job_name}/{AwsQuantumJob.RESULTS_FILENAME}" current_dir = Path.cwd() with tempfile.TemporaryDirectory() as temp_dir: os.chdir(temp_dir) try: - job.download_result() - assert ( - Path(AwsQuantumJob.RESULTS_TAR_FILENAME).exists() - and Path(downloaded_result).exists() - ) - # Check results match the expectations. assert job.result() == {"converged": True, "energy": -0.2} finally: diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py index b377bdebe..1fba5ebcb 100644 --- a/test/integ_tests/test_density_matrix_simulator.py +++ b/test/integ_tests/test_density_matrix_simulator.py @@ -7,7 +7,7 @@ from braket.aws import AwsDevice from braket.circuits import Circuit, Noise, Observable -SHOTS = 1000 +SHOTS = 500 DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" SIMULATOR_ARNS = [DM1_ARN] diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 4cb7de2b1..74b40afb6 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -28,8 +28,8 @@ @pytest.mark.parametrize( "arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN), (PULSE_ARN)] ) -def test_device_creation(arn, aws_session): - device = AwsDevice(arn, aws_session=aws_session) +def test_device_creation(arn, created_braket_devices): + device = created_braket_devices[arn] assert device.arn == arn assert device.name assert device.status @@ -39,17 +39,17 @@ def test_device_creation(arn, aws_session): @pytest.mark.parametrize("arn", [(PULSE_ARN)]) -def test_device_pulse_properties(arn, aws_session): - device = AwsDevice(arn, aws_session=aws_session) +def test_device_pulse_properties(arn, aws_session, created_braket_devices): + device = created_braket_devices[arn] assert device.ports assert device.frames -def test_device_across_regions(aws_session): +def test_device_across_regions(aws_session, created_braket_devices): # assert QPUs across different regions can be created using the same aws_session - AwsDevice(RIGETTI_ARN, aws_session) - AwsDevice(IONQ_ARN, aws_session) - AwsDevice(OQC_ARN, aws_session) + created_braket_devices[RIGETTI_ARN] + created_braket_devices[IONQ_ARN] + created_braket_devices[OQC_ARN] @pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN)]) @@ -59,8 +59,8 @@ def test_get_devices_arn(arn): @pytest.mark.parametrize("arn", [(PULSE_ARN)]) -def test_device_gate_calibrations(arn, aws_session): - device = AwsDevice(arn, aws_session=aws_session) +def test_device_gate_calibrations(arn, aws_session, created_braket_devices): + device = created_braket_devices[arn] assert device.gate_calibrations @@ -76,8 +76,8 @@ def test_get_devices_others(): assert result.status in statuses -def test_get_devices_all(): - result_arns = [result.arn for result in AwsDevice.get_devices()] +def test_get_devices_all(braket_devices): + result_arns = [result.arn for result in braket_devices] for arn in [RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: assert arn in result_arns @@ -127,17 +127,16 @@ def _validate_device(device: AwsDevice, active_providers: Set[str]): assert getattr(getattr(Devices, provider_name), device_name) == device.arn -def test_device_enum(): - aws_devices = AwsDevice.get_devices() - active_providers = _get_active_providers(aws_devices) +def test_device_enum(braket_devices, created_braket_devices): + active_providers = _get_active_providers(braket_devices) # validate all devices in API - for device in aws_devices: + for device in braket_devices: _validate_device(device, active_providers) # validate all devices in enum providers = [getattr(Devices, attr) for attr in dir(Devices) if not attr.startswith("__")] for provider in providers: for device_arn in provider: - device = AwsDevice(device_arn) + device = created_braket_devices[device_arn] _validate_device(device, active_providers) diff --git a/test/integ_tests/test_queue_information.py b/test/integ_tests/test_queue_information.py index 3398fde40..7e7590a80 100644 --- a/test/integ_tests/test_queue_information.py +++ b/test/integ_tests/test_queue_information.py @@ -11,7 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.aws import AwsDevice, AwsQuantumJob + +from braket.aws import AwsDevice from braket.aws.queue_information import ( HybridJobQueueInfo, QuantumTaskQueueInfo, @@ -47,15 +48,11 @@ def test_task_queue_position(): assert queue_information.message is None -def test_job_queue_position(aws_session): - job = AwsQuantumJob.create( - device=Devices.Amazon.SV1, - source_module="test/integ_tests/job_test_script.py", - entry_point="job_test_script:start_here", - aws_session=aws_session, - wait_until_complete=True, - hyperparameters={"test_case": "completed"}, - ) +def test_job_queue_position(aws_session, completed_quantum_job): + job = completed_quantum_job + + # Check the job is complete + job.result() # call the queue_position method. queue_information = job.queue_position() diff --git a/test/integ_tests/test_simulator_quantum_task.py b/test/integ_tests/test_simulator_quantum_task.py index 7b9e7d208..0dd4f3f30 100644 --- a/test/integ_tests/test_simulator_quantum_task.py +++ b/test/integ_tests/test_simulator_quantum_task.py @@ -46,7 +46,7 @@ # shots-based tests in this file have the capacity to fail rarely due to probabilistic checks. # this parameter can be adjusted if we find tests regularly failing. -SHOTS = 9000 +SHOTS = 5000 SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" DM1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/dm1" SIMULATOR_ARNS = [SV1_ARN, DM1_ARN] @@ -54,16 +54,18 @@ @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_no_result_types_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_no_result_types_bell_pair( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] no_result_types_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder, braket_simulators): + device = braket_simulators[simulator_arn] qubit_ordering_testing(device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder}) @@ -71,9 +73,9 @@ def test_qubit_ordering(simulator_arn, aws_session, s3_destination_folder): "simulator_arn, include_amplitude", list(zip(SIMULATOR_ARNS, [True, False])) ) def test_result_types_no_shots( - simulator_arn, include_amplitude, aws_session, s3_destination_folder + simulator_arn, include_amplitude, aws_session, s3_destination_folder, braket_simulators ): - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] result_types_zero_shots_bell_pair_testing( device, False, @@ -83,16 +85,20 @@ def test_result_types_no_shots( @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_nonzero_shots_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_nonzero_shots_bell_pair( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_nonzero_shots_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_bell_pair_full_probability(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_bell_pair_full_probability( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_bell_pair_full_probability_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @@ -100,41 +106,49 @@ def test_result_types_bell_pair_full_probability(simulator_arn, aws_session, s3_ @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_result_types_bell_pair_marginal_probability( - simulator_arn, aws_session, s3_destination_folder + simulator_arn, aws_session, s3_destination_folder, braket_simulators ): - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] result_types_bell_pair_marginal_probability_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_x_y(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_tensor_x_y( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_tensor_x_y_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_z_h_y(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_tensor_z_h_y( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_tensor_z_h_y_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_hermitian( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_tensor_z_z( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_tensor_z_z_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @@ -142,103 +156,117 @@ def test_result_types_tensor_z_z(simulator_arn, shots, aws_session, s3_destinati @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) def test_result_types_tensor_hermitian_hermitian( - simulator_arn, shots, aws_session, s3_destination_folder + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators ): - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] result_types_tensor_hermitian_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_y_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_tensor_y_hermitian( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_tensor_y_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_tensor_z_hermitian(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_tensor_z_hermitian( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_tensor_z_hermitian_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) -def test_result_types_all_selected(simulator_arn, shots, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_all_selected( + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_all_selected_testing( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_noncommuting(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_noncommuting( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_noncommuting_testing(device, {"s3_destination_folder": s3_destination_folder}) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) def test_result_types_noncommuting_flipped_targets( - simulator_arn, aws_session, s3_destination_folder + simulator_arn, aws_session, s3_destination_folder, braket_simulators ): - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] result_types_noncommuting_flipped_targets_testing( device, {"s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_result_types_noncommuting_all(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_result_types_noncommuting_all( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] result_types_noncommuting_all(device, {"s3_destination_folder": s3_destination_folder}) @pytest.mark.parametrize("simulator_arn,shots", ARNS_WITH_SHOTS) def test_result_types_observable_not_in_instructions( - simulator_arn, shots, aws_session, s3_destination_folder + simulator_arn, shots, aws_session, s3_destination_folder, braket_simulators ): - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] result_types_observable_not_in_instructions( device, {"shots": shots, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_multithreaded_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_multithreaded_bell_pair( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] multithreaded_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_batch_bell_pair(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_batch_bell_pair(simulator_arn, aws_session, s3_destination_folder, braket_simulators): + device = braket_simulators[simulator_arn] batch_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_bell_pair_openqasm(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_bell_pair_openqasm(simulator_arn, aws_session, s3_destination_folder, braket_simulators): + device = braket_simulators[simulator_arn] bell_pair_openqasm_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) -def test_bell_pair_openqasm_results(simulator_arn, aws_session, s3_destination_folder): - device = AwsDevice(simulator_arn, aws_session) +def test_bell_pair_openqasm_results( + simulator_arn, aws_session, s3_destination_folder, braket_simulators +): + device = braket_simulators[simulator_arn] openqasm_result_types_bell_pair_testing( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} ) -def test_openqasm_probability_results(aws_session, s3_destination_folder): +def test_openqasm_probability_results(aws_session, s3_destination_folder, braket_simulators): device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/dm1", aws_session) openqasm_noisy_circuit_1qubit_noise_full_probability( device, {"shots": SHOTS, "s3_destination_folder": s3_destination_folder} @@ -247,10 +275,12 @@ def test_openqasm_probability_results(aws_session, s3_destination_folder): @pytest.mark.parametrize("simulator_arn", SIMULATOR_ARNS) @pytest.mark.parametrize("num_layers", [50, 100, 500, 1000]) -def test_many_layers(simulator_arn, num_layers, aws_session, s3_destination_folder): +def test_many_layers( + simulator_arn, num_layers, aws_session, s3_destination_folder, braket_simulators +): num_qubits = 10 circuit = many_layers(num_qubits, num_layers) - device = AwsDevice(simulator_arn, aws_session) + device = braket_simulators[simulator_arn] tol = get_tol(SHOTS) result = device.run(circuit, shots=SHOTS, s3_destination_folder=s3_destination_folder).result() diff --git a/test/integ_tests/test_tensor_network_simulator.py b/test/integ_tests/test_tensor_network_simulator.py index 093781b62..5c406eadd 100644 --- a/test/integ_tests/test_tensor_network_simulator.py +++ b/test/integ_tests/test_tensor_network_simulator.py @@ -20,7 +20,7 @@ from braket.aws import AwsDevice from braket.circuits import Circuit -SHOTS = 1000 +SHOTS = 100 TN1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/tn1" SIMULATOR_ARNS = [TN1_ARN] diff --git a/tox.ini b/tox.ini index 467b9ab84..98a9b30e3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,12 @@ [tox] envlist = clean,linters,docs,unit-tests + +[testenv] +parallel_show_output = true +package = wheel +wheel_build_env = .pkg + [testenv:clean] deps = coverage skip_install = true @@ -18,6 +24,7 @@ extras = test [testenv:integ-tests] basepython = python3 +usedevelop=True # {posargs} contains additional arguments specified when invoking tox. e.g. tox -- -s -k test_foo.py deps = {[test-deps]deps} From a6e617abcdca6127f699755fa16d9c234761c5ab Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Mon, 19 Feb 2024 14:11:01 -0800 Subject: [PATCH 061/347] docs: add note about using env variables for endpoint (#878) --- src/braket/aws/aws_session.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 6c63a7e45..a1ae0c7bb 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -792,6 +792,11 @@ def copy_session( config = Config(max_pool_connections=max_connections) if max_connections else None session_region = self.boto_session.region_name new_region = region or session_region + + # note that this method does not copy a custom Braket endpoint URL, since those are + # region-specific. If you have an endpoint that you wish to be used by copied AwsSessions + # (i.e. for task batching), please use the `BRAKET_ENDPOINT` environment variable. + creds = self.boto_session.get_credentials() default_bucket = self._default_bucket if self._custom_default_bucket else None profile_name = self.boto_session.profile_name From 1c6a9626ee3de41006ef8874c229369d9363673e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:49:29 -0800 Subject: [PATCH 062/347] fix: use the caller's account id based on the session (#883) --- test/integ_tests/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 656353c67..2962c2048 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -162,11 +162,15 @@ def job_failed_name(request): @pytest.fixture(scope="session", autouse=True) def completed_quantum_job(aws_session, job_completed_name): - job = AwsQuantumJob(arn=f"arn:aws:braket:us-west-2:046073650652:job/{job_completed_name}") + account = boto3.client("sts").get_caller_identity().get("Account") + region = aws_session.region + job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_completed_name}") return job @pytest.fixture(scope="session", autouse=True) def failed_quantum_job(aws_session, job_failed_name): - job = AwsQuantumJob(arn=f"arn:aws:braket:us-west-2:046073650652:job/{job_failed_name}") + account = boto3.client("sts").get_caller_identity().get("Account") + region = aws_session.region + job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_failed_name}") return job From 7b1ffe44f2a4d57f5f3d345b43366e422ce9fb25 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:30:01 -0800 Subject: [PATCH 063/347] fix: remove test with job creation with qpu (#889) --- test/integ_tests/test_reservation_arn.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index e0736f802..98b87f075 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -18,7 +18,6 @@ from test_create_quantum_job import decorator_python_version from braket.aws import AwsDevice -from braket.aws.aws_quantum_job import AwsQuantumJob from braket.circuits import Circuit from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job @@ -56,19 +55,6 @@ def test_create_task_via_reservation_arn_on_simulator(reservation_arn): ) -def test_create_job_via_invalid_reservation_arn_on_qpu(aws_session, reservation_arn): - with pytest.raises(ClientError, match="Reservation arn is invalid"): - AwsQuantumJob.create( - device=Devices.IonQ.Harmony, - source_module="test/integ_tests/job_test_script.py", - entry_point="job_test_script:start_here", - wait_until_complete=True, - aws_session=aws_session, - hyperparameters={"test_case": "completed"}, - reservation_arn=reservation_arn, - ) - - @pytest.mark.xfail( (sys.version_info.major, sys.version_info.minor) != decorator_python_version(), raises=RuntimeError, From 576b15c47d455ccd504efbcdc35c87fd0fbc04bc Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 21 Feb 2024 21:46:27 +0000 Subject: [PATCH 064/347] prepare release v1.70.3 --- CHANGELOG.md | 8 ++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dd9c3495..57e0980b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## v1.70.3 (2024-02-21) + +### Bug Fixes and Other Changes + + * remove test with job creation with qpu + * use the caller's account id based on the session + * docs: add note about using env variables for endpoint + ## v1.70.2 (2024-02-14) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fc41f12eb..426481b74 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.3.dev0" +__version__ = "1.70.3" From c19120e32a30020008feb0cbc73f4f66f0288a12 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 21 Feb 2024 21:46:27 +0000 Subject: [PATCH 065/347] update development version to v1.70.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 426481b74..359a86456 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.3" +__version__ = "1.70.4.dev0" From 879133cbb11b9f80fdc0017ba83a12657bcac1f9 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 23 Feb 2024 12:53:59 -0800 Subject: [PATCH 066/347] feat: update log stream prefix for new jobs (#887) --- src/braket/aws/aws_quantum_job.py | 19 ++++++++--- .../cwl_insights_metrics_fetcher.py | 9 +++-- .../braket/aws/test_aws_quantum_job.py | 23 +++++++++++++ .../test_cwl_insights_metrics_fetcher.py | 34 +++++++++++++++++++ 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 711aeab1b..3c6dbe66e 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -286,6 +286,17 @@ def name(self) -> str: """str: The name of the quantum job.""" return self.metadata(use_cached_value=True).get("jobName") + @property + def _logs_prefix(self) -> str: + """str: the prefix for the job logs.""" + # jobs ARNs used to contain the job name and use a log prefix of `job-name` + # now job ARNs use a UUID and a log prefix of `job-name/UUID` + return ( + f"{self.name}" + if self.arn.endswith(self.name) + else f"{self.name}/{self.arn.split('/')[-1]}" + ) + def state(self, use_cached_value: bool = False) -> str: """The state of the quantum hybrid job. @@ -381,7 +392,6 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: ) log_group = AwsQuantumJob.LOG_GROUP - stream_prefix = f"{self.name}/" stream_names = [] # The list of log streams positions = {} # The current position in each stream, map of stream name -> position instance_count = self.metadata(use_cached_value=True)["instanceConfig"]["instanceCount"] @@ -395,7 +405,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: has_streams = logs.flush_log_streams( self._aws_session, log_group, - stream_prefix, + self._logs_prefix, stream_names, positions, instance_count, @@ -455,14 +465,15 @@ def metrics( """ fetcher = CwlInsightsMetricsFetcher(self._aws_session) metadata = self.metadata(True) - job_name = metadata["jobName"] job_start = None job_end = None if "startedAt" in metadata: job_start = int(metadata["startedAt"].timestamp()) if self.state() in AwsQuantumJob.TERMINAL_STATES and "endedAt" in metadata: job_end = int(math.ceil(metadata["endedAt"].timestamp())) - return fetcher.get_metrics_for_job(job_name, metric_type, statistic, job_start, job_end) + return fetcher.get_metrics_for_job( + self.name, metric_type, statistic, job_start, job_end, self._logs_prefix + ) def cancel(self) -> str: """Cancels the job. diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index be4e72541..be4b426d8 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -137,6 +137,7 @@ def get_metrics_for_job( statistic: MetricStatistic = MetricStatistic.MAX, job_start_time: int | None = None, job_end_time: int | None = None, + stream_prefix: str | None = None, ) -> dict[str, list[Union[str, float, int]]]: """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. @@ -150,6 +151,8 @@ def get_metrics_for_job( Default: 3 hours before job_end_time. job_end_time (int | None): If the hybrid job is complete, this should be the time at which the hybrid job finished. Default: current time. + stream_prefix (str | None): If a logs prefix is provided, it will be used instead + of the job name. Returns: dict[str, list[Union[str, float, int]]]: The metrics data, where the keys @@ -166,11 +169,11 @@ def get_metrics_for_job( query_end_time = job_end_time or int(time.time()) query_start_time = job_start_time or query_end_time - self.QUERY_DEFAULT_JOB_DURATION - # The hybrid job name needs to be unique to prevent jobs with similar names from being - # conflated. + stream_prefix = stream_prefix or job_name + query = ( f"fields @timestamp, @message " - f"| filter @logStream like /^{job_name}\\// " + f"| filter @logStream like /^{stream_prefix}\\// " f"| filter @message like /Metrics - /" ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 7f9dc1a84..8df2118cf 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -1104,3 +1104,26 @@ def test_bad_device_arn_format(aws_session): with pytest.raises(ValueError, match=device_not_found): AwsQuantumJob._initialize_session(aws_session, "bad-arn-format", logger) + + +def test_logs_prefix(job_region, quantum_job_name, aws_session, generate_get_job_response): + aws_session.get_job.return_value = generate_get_job_response(jobName=quantum_job_name) + + # old jobs with the `arn:.../job-name` style ARN use `job-name/` as the logs prefix + name_arn = f"arn:aws:braket:{job_region}:875981177017:job/{quantum_job_name}" + quantum_job = AwsQuantumJob(name_arn, aws_session) + assert quantum_job._logs_prefix == f"{quantum_job_name}" + + # jobs with the `arn:.../uuid` style ARN use `job-name/uuid/` as the logs prefix + uuid_1 = "UUID-123456789" + uuid_2 = "UUID-987654321" + uuid_arn_1 = f"arn:aws:braket:{job_region}:875981177017:job/{uuid_1}" + uuid_job_1 = AwsQuantumJob(uuid_arn_1, aws_session) + uuid_arn_2 = f"arn:aws:braket:{job_region}:875981177017:job/{uuid_2}" + uuid_job_2 = AwsQuantumJob(uuid_arn_2, aws_session) + assert ( + uuid_job_1._logs_prefix + == f"{quantum_job_name}/{uuid_1}" + != uuid_job_2._logs_prefix + == f"{quantum_job_name}/{uuid_2}" + ) diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py index 17027cec4..5b3eb7e42 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py @@ -80,6 +80,40 @@ def test_get_all_metrics_complete_results(mock_add_metrics, mock_get_metrics, aw assert result == expected_result +@patch("braket.jobs.metrics_data.cwl_insights_metrics_fetcher.LogMetricsParser.get_parsed_metrics") +@patch("braket.jobs.metrics_data.cwl_insights_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_all_metrics_complete_results_stream_prefix( + mock_add_metrics, mock_get_metrics, aws_session +): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.start_query.return_value = {"queryId": "test"} + logs_client_mock.get_query_results.return_value = { + "status": "Complete", + "results": EXAMPLE_METRICS_LOG_LINES, + } + expected_result = {"Test": [0]} + mock_get_metrics.return_value = expected_result + + fetcher = CwlInsightsMetricsFetcher(aws_session) + + result = fetcher.get_metrics_for_job( + "test_job", job_start_time=1, job_end_time=2, stream_prefix="test_job/uuid" + ) + logs_client_mock.get_query_results.assert_called_with(queryId="test") + logs_client_mock.start_query.assert_called_with( + logGroupName="/aws/braket/jobs", + startTime=1, + endTime=2, + queryString="fields @timestamp, @message | filter @logStream like /^test_job/uuid\\//" + " | filter @message like /Metrics - /", + limit=10000, + ) + assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST + assert result == expected_result + + def test_get_all_metrics_timeout(aws_session): logs_client_mock = Mock() aws_session.logs_client = logs_client_mock From 09099c0927d5213cb763976855affaaed9615bb5 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Feb 2024 16:14:33 +0000 Subject: [PATCH 067/347] prepare release v1.71.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e0980b3..9b4cdf903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.71.0 (2024-02-26) + +### Features + + * update log stream prefix for new jobs + ## v1.70.3 (2024-02-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 359a86456..86b26bf69 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.4.dev0" +__version__ = "1.71.0" From 3769c773246a721bd323fe4e04dac5c4be11d50c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Feb 2024 16:14:33 +0000 Subject: [PATCH 068/347] update development version to v1.71.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 86b26bf69..fde388ff2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.71.0" +__version__ = "1.71.1.dev0" From ac47de89a721c72d3534c25cf0a152398bc21737 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 26 Feb 2024 11:20:33 -0800 Subject: [PATCH 069/347] feat: FreeParameterExpression division (#885) --- .../parametric/free_parameter_expression.py | 9 +++++++++ .../test_free_parameter_expression.py | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index d3ad91259..cc339fe13 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -151,6 +151,15 @@ def __mul__(self, other: FreeParameterExpression): def __rmul__(self, other: FreeParameterExpression): return FreeParameterExpression(other * self.expression) + def __truediv__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression / other.expression) + else: + return FreeParameterExpression(self.expression / other) + + def __rtruediv__(self, other: FreeParameterExpression): + return FreeParameterExpression(other / self.expression) + def __pow__(self, other: FreeParameterExpression, modulo: float = None): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) diff --git a/test/unit_tests/braket/parametric/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py index 370d45083..1bf6818bf 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -80,7 +80,6 @@ def test_commutativity(): def test_add(): add_expr = FreeParameter("theta") + FreeParameter("theta") expected = FreeParameterExpression(2 * FreeParameter("theta")) - assert add_expr == expected @@ -89,14 +88,12 @@ def test_sub(): expected = FreeParameterExpression(FreeParameter("theta")) - FreeParameterExpression( FreeParameter("alpha") ) - assert sub_expr == expected def test_r_sub(): r_sub_expr = 1 - FreeParameter("theta") expected = FreeParameterExpression(1 - FreeParameter("theta")) - assert r_sub_expr == expected @@ -106,6 +103,20 @@ def test_mul(): assert mul_expr == expected +def test_truediv(): + truediv_expr = FreeParameter("theta") / FreeParameter("alpha") + expected = FreeParameterExpression(FreeParameter("theta")) / FreeParameterExpression( + FreeParameter("alpha") + ) + assert truediv_expr == expected + + +def test_r_truediv(): + r_truediv_expr = 1 / FreeParameter("theta") + expected = FreeParameterExpression(1 / FreeParameter("theta")) + assert r_truediv_expr == expected + + def test_pow(): mul_expr = FreeParameter("theta") ** FreeParameter("alpha") * 2 expected = FreeParameterExpression(FreeParameter("theta") ** FreeParameter("alpha") * 2) From 9d711554dd18f371e634d77cfcaadea001b6669b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 27 Feb 2024 16:13:22 +0000 Subject: [PATCH 070/347] prepare release v1.72.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4cdf903..9f5d4b9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.0 (2024-02-27) + +### Features + + * FreeParameterExpression division + ## v1.71.0 (2024-02-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fde388ff2..b0b886fa4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.71.1.dev0" +__version__ = "1.72.0" From 51360693af5f2b282ff37ca38ab766ce8fe64613 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 27 Feb 2024 16:13:22 +0000 Subject: [PATCH 071/347] update development version to v1.72.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b0b886fa4..02e831d5b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.0" +__version__ = "1.72.1.dev0" From 0f9ff8d80a50203b632acf4c4a021a15cabf0141 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 27 Feb 2024 16:27:07 -0800 Subject: [PATCH 072/347] fix: escape slash in metrics prefix (#897) --- src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py | 2 +- .../jobs/metrics_data/test_cwl_insights_metrics_fetcher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index be4b426d8..b2d12fe36 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -169,7 +169,7 @@ def get_metrics_for_job( query_end_time = job_end_time or int(time.time()) query_start_time = job_start_time or query_end_time - self.QUERY_DEFAULT_JOB_DURATION - stream_prefix = stream_prefix or job_name + stream_prefix = (stream_prefix or job_name).replace("/", "\\/") query = ( f"fields @timestamp, @message " diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py index 5b3eb7e42..72f9c4db5 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py @@ -106,7 +106,7 @@ def test_get_all_metrics_complete_results_stream_prefix( logGroupName="/aws/braket/jobs", startTime=1, endTime=2, - queryString="fields @timestamp, @message | filter @logStream like /^test_job/uuid\\//" + queryString="fields @timestamp, @message | filter @logStream like /^test_job\\/uuid\\//" " | filter @message like /Metrics - /", limit=10000, ) From 732661dcbdfe0039cda932d4084b774390c9c238 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Feb 2024 00:41:54 +0000 Subject: [PATCH 073/347] prepare release v1.72.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5d4b9c0..22e4bfd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.1 (2024-02-28) + +### Bug Fixes and Other Changes + + * escape slash in metrics prefix + ## v1.72.0 (2024-02-27) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 02e831d5b..799802700 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.1.dev0" +__version__ = "1.72.1" From f4378178155c5c31bb886bf9abfac181e9379ebb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Feb 2024 00:41:54 +0000 Subject: [PATCH 074/347] update development version to v1.72.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 799802700..6143a872f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.1" +__version__ = "1.72.2.dev0" From 0370766feca0be3a5a3128b44f65cf5132ca1099 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:09:27 -0800 Subject: [PATCH 075/347] infra: pin numpy to less than 2.x until we prepare for the migration (#890) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b336a2790..16f96bd31 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ "cloudpickle==2.2.1", "nest-asyncio", "networkx", - "numpy", + "numpy<2", "openpulse", "openqasm3", "sympy", From 431410066e9ba75f6b2b4a96fb8ad6ade2c61e5d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:09:01 -0500 Subject: [PATCH 076/347] fix: validate FreeParameter name (#895) * validate numerical free parameters * simplify validation * do not use _validate_name --------- Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Co-authored-by: Cody Wang --- src/braket/circuits/circuit.py | 5 +- src/braket/parametric/free_parameter.py | 11 ++++- .../parametric/free_parameter_expression.py | 2 +- src/braket/pulse/pulse_sequence.py | 5 +- .../braket/circuits/test_circuit.py | 46 ++++++++++++++++++- .../braket/parametric/test_free_parameter.py | 10 +++- 6 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b3063c444..8b696f17f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -19,6 +19,7 @@ import numpy as np import oqpy +from sympy import Expr from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -472,7 +473,9 @@ def add_instruction( if self._check_for_params(instruction): for param in instruction.operator.parameters: - if isinstance(param, FreeParameterExpression): + if isinstance(param, FreeParameterExpression) and isinstance( + param.expression, Expr + ): free_params = param.expression.free_symbols for parameter in free_params: self._parameters.add(FreeParameter(parameter.name)) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 9d5826520..78fbe45b3 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -47,7 +47,7 @@ def __init__(self, name: str): >>> param1 = FreeParameter("theta") >>> param1 = FreeParameter("\u03B8") """ - self._name = Symbol(name) + self._set_name(name) super().__init__(expression=self._name) @property @@ -87,6 +87,15 @@ def __repr__(self) -> str: """ return self.name + def _set_name(self, name: str) -> None: + if not name: + raise ValueError("FreeParameter names must be non empty") + if not isinstance(name, str): + raise TypeError("FreeParameter names must be strings") + if not name[0].isalpha() and not name[0] == "_": + raise ValueError("FreeParameter names must start with a letter or an underscore") + self._name = Symbol(name) + def to_dict(self) -> dict: return { "__class__": self.__class__.__name__, diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index cc339fe13..fdd2f5474 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -107,7 +107,7 @@ def _parse_string_expression(self, expression: str) -> FreeParameterExpression: return self._eval_operation(ast.parse(expression, mode="eval").body) def _eval_operation(self, node: Any) -> FreeParameterExpression: - if isinstance(node, ast.Num): + if isinstance(node, ast.Constant): return FreeParameterExpression(node.n) elif isinstance(node, ast.Name): return FreeParameterExpression(sympy.Symbol(node.id)) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index e6d78f11f..a788b38c0 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -20,6 +20,7 @@ from openpulse import ast from oqpy import BitVar, PhysicalQubits, Program +from sympy import Expr from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import FreeParameterExpression @@ -330,7 +331,9 @@ def _register_free_parameters( self, parameter: Union[float, FreeParameterExpression], ) -> None: - if isinstance(parameter, FreeParameterExpression): + if isinstance(parameter, FreeParameterExpression) and isinstance( + parameter.expression, Expr + ): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 3cf40fd89..91b19dba9 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -21,6 +21,7 @@ AsciiCircuitDiagram, Circuit, FreeParameter, + FreeParameterExpression, Gate, Instruction, Moments, @@ -745,6 +746,44 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): assert circ.to_ir() == expected +@pytest.mark.parametrize( + "circuit, serialization_properties, expected_ir", + [ + ( + Circuit() + .rx(0, 0.15) + .ry(1, FreeParameterExpression("0.3")) + .rx(2, 3 * FreeParameterExpression(1)), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "rx(0.15) q[0];", + "ry(0.3) q[1];", + "rx(3) q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + ] + ), + inputs={}, + ), + ), + ], +) +def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): + assert ( + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + == expected_ir + ) + + @pytest.mark.parametrize( "circuit, serialization_properties, expected_ir", [ @@ -1019,7 +1058,9 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ), ], ) -def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): +def test_circuit_to_ir_openqasm_with_gate_calibrations( + circuit, serialization_properties, expected_ir, gate_calibrations +): copy_of_gate_calibrations = gate_calibrations.copy() assert ( circuit.to_ir( @@ -1962,7 +2003,8 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "input float theta;" "bit[1] b;", + "input float theta;", + "bit[1] b;", "qubit[1] q;", "rx(theta) q[0];", "rx(2*theta) q[0];", diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py index 816bc0cee..b94c60a97 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -21,9 +21,15 @@ def free_parameter(): return FreeParameter("theta") -@pytest.mark.xfail(raises=TypeError) def test_bad_input(): - FreeParameter(6) + with pytest.raises(ValueError, match="FreeParameter names must be non empty"): + FreeParameter("") + with pytest.raises(TypeError, match="FreeParameter names must be strings"): + FreeParameter(6) + with pytest.raises( + ValueError, match="FreeParameter names must start with a letter or an underscore" + ): + FreeParameter(".2") def test_is_free_param(free_parameter): From f4d642ae00199ccdb17cfd4a3e1043069c9ea670 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:38:36 -0800 Subject: [PATCH 077/347] test: Resolve integration test job ARNs becoming UUIDs (#900) --- test/integ_tests/conftest.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 2962c2048..2a56d8074 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -161,16 +161,22 @@ def job_failed_name(request): @pytest.fixture(scope="session", autouse=True) -def completed_quantum_job(aws_session, job_completed_name): - account = boto3.client("sts").get_caller_identity().get("Account") - region = aws_session.region - job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_completed_name}") - return job +def completed_quantum_job(job_completed_name): + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_completed_name + ][0] + + return AwsQuantumJob(arn=job_arn) @pytest.fixture(scope="session", autouse=True) -def failed_quantum_job(aws_session, job_failed_name): - account = boto3.client("sts").get_caller_identity().get("Account") - region = aws_session.region - job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_failed_name}") - return job +def failed_quantum_job(job_failed_name): + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_failed_name + ][0] + + return AwsQuantumJob(arn=job_arn) From 4b3f9906de850e6564155a46c846fa2745b828ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:41:05 -0800 Subject: [PATCH 078/347] infra: bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.12 (#901) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.11 to 1.8.12. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf...e53eb8b103ffcb59469888563dc324e3c8ba6f06) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index b12cde750..0ec7ff9b2 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # release/v1 + uses: pypa/gh-action-pypi-publish@e53eb8b103ffcb59469888563dc324e3c8ba6f06 # release/v1 with: password: ${{ secrets.pypi_token }} From 8f2f2a2ed9159e0720e1484ed1e9e2292f518a4a Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Mar 2024 16:13:59 +0000 Subject: [PATCH 079/347] prepare release v1.72.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e4bfd5a..62d625ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.2 (2024-03-04) + +### Bug Fixes and Other Changes + + * validate FreeParameter name + ## v1.72.1 (2024-02-28) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6143a872f..d6dc5d352 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.2.dev0" +__version__ = "1.72.2" From 4ff8f6f5816fe7aab344561fbecaf9732ebff14e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Mar 2024 16:13:59 +0000 Subject: [PATCH 080/347] update development version to v1.72.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d6dc5d352..b77e9d94c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.2" +__version__ = "1.72.3.dev0" From 4b9759903467ee7817671f6f109a35d6b93a60ce Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 4 Mar 2024 14:46:00 -0800 Subject: [PATCH 081/347] infra: Use Codecov token (#903) --- .github/workflows/python-package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 74b49db9e..f216d2c19 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,4 +37,6 @@ jobs: tox -e unit-tests - name: Upload coverage report to Codecov uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 + with: + token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From b4245ea1050cb9d2ee24b43c624a5ff7d606a96c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:53:33 -0800 Subject: [PATCH 082/347] infra: bump codecov/codecov-action from 3.1.5 to 4.0.1 (#868) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.5 to 4.0.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0...e0b68c6749509c5f83f984dd99a76a1c1a231044) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f216d2c19..516f93c70 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From c49176bc3427f747a35f3b23710cd24d6d8e6828 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:10:51 -0500 Subject: [PATCH 083/347] feature: update circuit drawing (#846) * replace control symbols * use box drawing characters * fix tests * switch to large b/w circle * first attempt to box symbols * fix single qubit circuit * rewrite first tests * first try to draw verbatim box * revamp verbatim box * update tests and skip outdated * modify last test * fix connections * fix linter * update more tests * update verbatix box tests * update last tests, left 6 xfail * remove margin * add connecting edges * code coverage * finish code coverage * decomplexify * add xfail test * move AsciiDiagram to BoxDrawingDiagram * keep ascii diagrams as legacy * add default_diagram_builder field * replace back circles by C/N * remove duplicate code * more simplification * add comment * add back build_diagram for readibilty * use a _build_box method * simplify _build_parameters * remove unnecessary code * mutualize create_output * add another xfail test * fix linters * fix misalignment * fix linters * cleanup * make _fill_symbol private * clean an branching condition * draw swap gates with x * remove commented tests * clean implementation * keep AsciiCircuitDiagram as default for now * reorganize class structure * fix docstring * make class-specific method explicit * fix linters * fix typos * remove forgotten argument * use a utilities class * do not use a TextCircuitDiagramUtilities * rename methods * rename to text_circuit_diagram_utils * use cls var * first changes according to PR feedback * Attempt at simplification (#898) * add docstrings and rename box_drawing_circuit_diagram after merge * standardize type hints of _draw_symbol * small changes according to PR feedback. * change a staticmethod to a classmethod --------- Co-authored-by: Cody Wang Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> --- src/braket/circuits/__init__.py | 7 +- src/braket/circuits/ascii_circuit_diagram.py | 403 +------ src/braket/circuits/circuit.py | 6 +- .../ascii_circuit_diagram.py | 195 ++++ .../text_circuit_diagram.py | 265 +++++ .../text_circuit_diagram_utils.py | 197 ++++ .../unicode_circuit_diagram.py | 283 +++++ .../circuits/test_ascii_circuit_diagram.py | 8 +- .../braket/circuits/test_circuit.py | 4 +- .../circuits/test_unicode_circuit_diagram.py | 1021 +++++++++++++++++ 10 files changed, 1981 insertions(+), 408 deletions(-) create mode 100644 src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py create mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram.py create mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py create mode 100644 src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py create mode 100644 test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index d2788746c..a5fb52980 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -20,7 +20,6 @@ result_types, ) from braket.circuits.angled_gate import AngledGate, DoubleAngledGate # noqa: F401 -from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram # noqa: F401 from braket.circuits.circuit import Circuit # noqa: F401 from braket.circuits.circuit_diagram import CircuitDiagram # noqa: F401 from braket.circuits.compiler_directive import CompilerDirective # noqa: F401 @@ -38,3 +37,9 @@ from braket.circuits.qubit import Qubit, QubitInput # noqa: F401 from braket.circuits.qubit_set import QubitSet, QubitSetInput # noqa: F401 from braket.circuits.result_type import ObservableResultType, ResultType # noqa: F401 +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 + AsciiCircuitDiagram, +) +from braket.circuits.text_diagram_builders.unicode_circuit_diagram import ( # noqa: F401 + UnicodeCircuitDiagram, +) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index c255377b5..accbce161 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -11,401 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import annotations - -from functools import reduce -from typing import Union - -import braket.circuits.circuit as cir -from braket.circuits.circuit_diagram import CircuitDiagram -from braket.circuits.compiler_directive import CompilerDirective -from braket.circuits.gate import Gate -from braket.circuits.instruction import Instruction -from braket.circuits.moments import MomentType -from braket.circuits.noise import Noise -from braket.circuits.result_type import ResultType -from braket.registers.qubit import Qubit -from braket.registers.qubit_set import QubitSet - - -class AsciiCircuitDiagram(CircuitDiagram): - """Builds ASCII string circuit diagrams.""" - - @staticmethod - def build_diagram(circuit: cir.Circuit) -> str: - """Build an ASCII string circuit diagram. - - Args: - circuit (cir.Circuit): Circuit for which to build a diagram. - - Returns: - str: ASCII string circuit diagram. - """ - if not circuit.instructions: - return "" - - if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): - return f"Global phase: {circuit.global_phase}" - - circuit_qubits = circuit.qubits - circuit_qubits.sort() - - y_axis_str, global_phase = AsciiCircuitDiagram._prepare_diagram_vars( - circuit, circuit_qubits - ) - - time_slices = circuit.moments.time_slices() - column_strs = [] - - # Moment columns - for time, instructions in time_slices.items(): - global_phase = AsciiCircuitDiagram._compute_moment_global_phase( - global_phase, instructions - ) - moment_str = AsciiCircuitDiagram._ascii_diagram_column_set( - str(time), circuit_qubits, instructions, global_phase - ) - column_strs.append(moment_str) - - # Result type columns - additional_result_types, target_result_types = AsciiCircuitDiagram._categorize_result_types( - circuit.result_types - ) - if target_result_types: - column_strs.append( - AsciiCircuitDiagram._ascii_diagram_column_set( - "Result Types", circuit_qubits, target_result_types, global_phase - ) - ) - - # Unite strings - lines = y_axis_str.split("\n") - for col_str in column_strs: - for i, line_in_col in enumerate(col_str.split("\n")): - lines[i] += line_in_col - - # Time on top and bottom - lines.append(lines[0]) - - if global_phase: - lines.append(f"\nGlobal phase: {global_phase}") - - # Additional result types line on bottom - if additional_result_types: - lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") - - # A list of parameters in the circuit to the currently assigned values. - if circuit.parameters: - lines.append( - "\nUnassigned parameters: " - f"{sorted(circuit.parameters, key=lambda param: param.name)}." - ) - - return "\n".join(lines) - - @staticmethod - def _prepare_diagram_vars( - circuit: cir.Circuit, circuit_qubits: QubitSet - ) -> tuple[str, float | None]: - # Y Axis Column - y_axis_width = len(str(int(max(circuit_qubits)))) - y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) - - global_phase = None - if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): - y_axis_str += "{0:{width}} : |\n".format("GP", width=y_axis_width) - global_phase = 0 - - for qubit in circuit_qubits: - y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) - y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) - - return y_axis_str, global_phase - - @staticmethod - def _compute_moment_global_phase( - global_phase: float | None, items: list[Instruction] - ) -> float | None: - """Compute the integrated phase at a certain moment. - - Args: - global_phase (float | None): The integrated phase up to the computed moment - items (list[Instruction]): list of instructions - - Returns: - float | None: The updated integrated phase. - """ - moment_phase = 0 - for item in items: - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - moment_phase += item.operator.angle - return global_phase + moment_phase if global_phase is not None else None - - @staticmethod - def _ascii_group_items( - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - ) -> list[tuple[QubitSet, list[Instruction]]]: - """Group instructions in a moment for ASCII diagram - - Args: - circuit_qubits (QubitSet): set of qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - - Returns: - list[tuple[QubitSet, list[Instruction]]]: list of grouped instructions or result types. - """ - groupings = [] - for item in items: - # Can only print Gate and Noise operators for instructions at the moment - if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective) - ): - continue - - # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range - # to an empty list and we just add it to the first group below. - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - qubit_range = QubitSet() - elif (isinstance(item, ResultType) and not item.target) or ( - isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) - ): - qubit_range = circuit_qubits - else: - if isinstance(item.target, list): - target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target = item.target - control = getattr(item, "control", QubitSet()) - target_and_control = target.union(control) - qubit_range = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) - - found_grouping = False - for group in groupings: - qubits_added = group[0] - instr_group = group[1] - # Take into account overlapping multi-qubit gates - if not qubits_added.intersection(set(qubit_range)): - instr_group.append(item) - qubits_added.update(qubit_range) - found_grouping = True - break - - if not found_grouping: - groupings.append((qubit_range, [item])) - - return groupings - - @staticmethod - def _categorize_result_types( - result_types: list[ResultType], - ) -> tuple[list[str], list[ResultType]]: - """Categorize result types into result types with target and those without. - - Args: - result_types (list[ResultType]): list of result types - - Returns: - tuple[list[str], list[ResultType]]: first element is a list of result types - without `target` attribute; second element is a list of result types with - `target` attribute - """ - additional_result_types = [] - target_result_types = [] - for result_type in result_types: - if hasattr(result_type, "target"): - target_result_types.append(result_type) - else: - additional_result_types.extend(result_type.ascii_symbols) - return additional_result_types, target_result_types - - @staticmethod - def _ascii_diagram_column_set( - col_title: str, - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - global_phase: float | None, - ) -> str: - """Return a set of columns in the ASCII string diagram of the circuit for a list of items. - - Args: - col_title (str): title of column set - circuit_qubits (QubitSet): qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - global_phase (float | None): the integrated global phase up to this set - - Returns: - str: An ASCII string diagram for the column set. - """ - # Group items to separate out overlapping multi-qubit items - groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) - - column_strs = [ - AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1], global_phase) - for grouping in groupings - ] - - # Unite column strings - lines = column_strs[0].split("\n") - for column_str in column_strs[1:]: - for i, moment_line in enumerate(column_str.split("\n")): - lines[i] += moment_line - - # Adjust for column title width - col_title_width = len(col_title) - symbols_width = len(lines[0]) - 1 - if symbols_width < col_title_width: - diff = col_title_width - symbols_width - for i in range(len(lines) - 1): - if lines[i].endswith("-"): - lines[i] += "-" * diff - else: - lines[i] += " " - - first_line = "{:^{width}}|\n".format(col_title, width=len(lines[0]) - 1) - - return first_line + "\n".join(lines) - - @staticmethod - def _ascii_diagram_column( - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - global_phase: float | None = None, - ) -> str: - """Return a column in the ASCII string diagram of the circuit for a given list of items. - - Args: - circuit_qubits (QubitSet): qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - global_phase (float | None): the integrated global phase up to this column - - Returns: - str: an ASCII string diagram for the specified moment in time for a column. - """ - symbols = {qubit: "-" for qubit in circuit_qubits} - margins = {qubit: " " for qubit in circuit_qubits} - - for item in items: - if isinstance(item, ResultType) and not item.target: - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) - elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbol = item.ascii_symbols[0] - marker = "*" * len(ascii_symbol) - num_after = len(circuit_qubits) - 1 - after = ["|"] * (num_after - 1) + ([marker] if num_after else []) - ascii_symbols = [ascii_symbol, *after] - elif ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = QubitSet() - qubits = circuit_qubits - ascii_symbols = "-" * len(circuit_qubits) - else: - if isinstance(item.target, list): - target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target_qubits = item.target - control_qubits = getattr(item, "control", QubitSet()) - map_control_qubit_states = AsciiCircuitDiagram._build_map_control_qubits( - item, control_qubits - ) - - target_and_control = target_qubits.union(control_qubits) - qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) - - ascii_symbols = item.ascii_symbols - - for qubit in qubits: - # Determine if the qubit is part of the item or in the middle of a - # multi qubit item. - if qubit in target_qubits: - item_qubit_index = [ - index for index, q in enumerate(target_qubits) if q == qubit - ][0] - power_string = ( - f"^{power}" - if ( - (power := getattr(item, "power", 1)) != 1 - # this has the limitation of not printing the power - # when a user has a gate genuinely named C, but - # is necessary to enable proper printing of custom - # gates with built-in control qubits - and ascii_symbols[item_qubit_index] != "C" - ) - else "" - ) - symbols[qubit] = ( - f"({ascii_symbols[item_qubit_index]}{power_string})" - if power_string - else ascii_symbols[item_qubit_index] - ) - elif qubit in control_qubits: - symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" - else: - symbols[qubit] = "|" - - # Set the margin to be a connector if not on the first qubit - if target_and_control and qubit != min(target_and_control): - margins[qubit] = "|" - - output = AsciiCircuitDiagram._create_output(symbols, margins, circuit_qubits, global_phase) - return output - - @staticmethod - def _create_output( - symbols: dict[Qubit, str], - margins: dict[Qubit, str], - qubits: QubitSet, - global_phase: float | None, - ) -> str: - symbols_width = max([len(symbol) for symbol in symbols.values()]) - output = "" - - if global_phase is not None: - global_phase_str = ( - f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) - ) - symbols_width = max([symbols_width, len(global_phase_str)]) - output += "{0:{fill}{align}{width}}|\n".format( - global_phase_str, - fill=" ", - align="^", - width=symbols_width, - ) - - for qubit in qubits: - output += "{0:{width}}\n".format(margins[qubit], width=symbols_width + 1) - output += "{0:{fill}{align}{width}}\n".format( - symbols[qubit], fill="-", align="<", width=symbols_width + 1 - ) - return output - - @staticmethod - def _build_map_control_qubits(item: Instruction, control_qubits: QubitSet) -> dict(Qubit, int): - control_state = getattr(item, "control_state", None) - if control_state is not None: - map_control_qubit_states = dict(zip(control_qubits, control_state)) - else: - map_control_qubit_states = {qubit: 1 for qubit in control_qubits} - - return map_control_qubit_states +# Moving ascii_circuit_diagram.py into the text_diagram_builders folder in order +# to group all classes that print circuits in a text format. +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 + AsciiCircuitDiagram, +) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 8b696f17f..36f0e68fb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -22,7 +22,6 @@ from sympy import Expr from braket.circuits import compiler_directives -from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -51,6 +50,7 @@ QubitReferenceType, SerializationProperties, ) +from braket.circuits.text_diagram_builders.unicode_circuit_diagram import UnicodeCircuitDiagram from braket.circuits.unitary_calculation import calculate_unitary_big_endian from braket.default_simulator.openqasm.interpreter import Interpreter from braket.ir.jaqcd import Program as JaqcdProgram @@ -1086,7 +1086,7 @@ def adjoint(self) -> Circuit: circ.add_result_type(result_type) return circ - def diagram(self, circuit_diagram_class: type = AsciiCircuitDiagram) -> str: + def diagram(self, circuit_diagram_class: type = UnicodeCircuitDiagram) -> str: """Get a diagram for the current circuit. Args: @@ -1498,7 +1498,7 @@ def __repr__(self) -> str: ) def __str__(self): - return self.diagram(AsciiCircuitDiagram) + return self.diagram() def __eq__(self, other: Circuit): if isinstance(other, Circuit): diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py new file mode 100644 index 000000000..3106afc47 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -0,0 +1,195 @@ +# 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. + +from __future__ import annotations + +from functools import reduce +from typing import Literal, Union + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram import TextCircuitDiagram +from braket.registers.qubit_set import QubitSet + + +class AsciiCircuitDiagram(TextCircuitDiagram): + """Builds ASCII string circuit diagrams.""" + + @staticmethod + def build_diagram(circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + return AsciiCircuitDiagram._build(circuit) + + @classmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + return "|" + + @classmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + return "-" + + @classmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + return 0 + + @classmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + return 1 + + @classmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + return 0 + + @classmethod + def _duplicate_time_at_bottom(cls, lines: str) -> None: + # duplicate times after an empty line + lines.append(lines[0]) + + @classmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None = None, + ) -> str: + """Return a column in the ASCII string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: an ASCII string diagram for the specified moment in time for a column. + """ + symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} + connections = {qubit: "none" for qubit in circuit_qubits} + + for item in items: + if isinstance(item, ResultType) and not item.target: + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbol = item.ascii_symbols[0] + marker = "*" * len(ascii_symbol) + num_after = len(circuit_qubits) - 1 + after = ["|"] * (num_after - 1) + ([marker] if num_after else []) + ascii_symbols = [ascii_symbol, *after] + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = QubitSet() + qubits = circuit_qubits + ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) + else: + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target + control_qubits = getattr(item, "control", QubitSet()) + control_state = getattr(item, "control_state", "1" * len(control_qubits)) + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + + target_and_control = target_qubits.union(control_qubits) + qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + + ascii_symbols = item.ascii_symbols + + for qubit in qubits: + # Determine if the qubit is part of the item or in the middle of a + # multi qubit item. + if qubit in target_qubits: + item_qubit_index = [ + index for index, q in enumerate(target_qubits) if q == qubit + ][0] + power_string = ( + f"^{power}" + if ( + (power := getattr(item, "power", 1)) != 1 + # this has the limitation of not printing the power + # when a user has a gate genuinely named C, but + # is necessary to enable proper printing of custom + # gates with built-in control qubits + and ascii_symbols[item_qubit_index] != "C" + ) + else "" + ) + symbols[qubit] = ( + f"({ascii_symbols[item_qubit_index]}{power_string})" + if power_string + else ascii_symbols[item_qubit_index] + ) + elif qubit in control_qubits: + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" + else: + symbols[qubit] = "|" + + # Set the margin to be a connector if not on the first qubit + if target_and_control and qubit != min(target_and_control): + connections[qubit] = "above" + + output = cls._create_output(symbols, connections, circuit_qubits, global_phase) + return output + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + def _draw_symbol( + cls, symbol: str, symbols_width: int, connection: Literal["above", "below", "both", "none"] + ) -> str: + """Create a string representing the symbol. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): character indicating + if the gate also involve a qubit with a lower index. + + Returns: + str: a string representing the symbol. + """ + connection_char = cls._vertical_delimiter() if connection in ["above"] else " " + output = "{0:{width}}\n".format(connection_char, width=symbols_width + 1) + output += "{0:{fill}{align}{width}}\n".format( + symbol, fill=cls._qubit_line_character(), align="<", width=symbols_width + 1 + ) + return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py new file mode 100644 index 000000000..c8bfa2650 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Literal, Union + +import braket.circuits.circuit as cir +from braket.circuits.circuit_diagram import CircuitDiagram +from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram_utils import ( + _add_footers, + _categorize_result_types, + _compute_moment_global_phase, + _group_items, + _prepare_qubit_identifier_column, + _unite_strings, +) +from braket.registers.qubit import Qubit +from braket.registers.qubit_set import QubitSet + + +class TextCircuitDiagram(CircuitDiagram, ABC): + """Abstract base class for text circuit diagrams.""" + + @classmethod + @abstractmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + + @classmethod + @abstractmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + + @classmethod + @abstractmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + + @classmethod + @abstractmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + + @classmethod + @abstractmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + + @classmethod + @abstractmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Instruction | ResultType], + global_phase: float | None = None, + ) -> str: + """Return a column in the string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Instruction | ResultType]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: a string diagram for the specified moment in time for a column. + """ + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + @abstractmethod + def _draw_symbol( + cls, + symbol: str, + symbols_width: int, + connection: Literal["above", "below", "both", "none"], + ) -> str: + """Create a string representing the symbol inside a box. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): specifies if a connection + will be drawn above and/or below the box. + + Returns: + str: a string representing the symbol. + """ + + @classmethod + def _build(cls, circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + The procedure follows as: + 1. Prepare the first column composed of the qubit identifiers + 2. Construct the circuit as a list of columns by looping through the + time slices. A column is a string with rows separated via '\n' + a. compute the instantaneous global phase + b. create the column corresponding to the current moment + 3. Add result types at the end of the circuit + 4. Join the columns to get a list of qubit lines + 5. Add a list of optional parameters: + a. the total global phase + b. results types that do not have any target such as statevector + c. the list of unassigned parameters + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + if not circuit.instructions: + return "" + + if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + return f"Global phase: {circuit.global_phase}" + + circuit_qubits = circuit.qubits + circuit_qubits.sort() + + y_axis_str, global_phase = _prepare_qubit_identifier_column( + circuit, + circuit_qubits, + cls._vertical_delimiter(), + cls._qubit_line_character(), + cls._qubit_line_spacing_above(), + cls._qubit_line_spacing_below(), + ) + + column_strs = [] + + global_phase, additional_result_types = cls._build_columns( + circuit, circuit_qubits, global_phase, column_strs + ) + + # Unite strings + lines = _unite_strings(y_axis_str, column_strs) + cls._duplicate_time_at_bottom(lines) + + return _add_footers(lines, circuit, global_phase, additional_result_types) + + @classmethod + def _build_columns( + cls, + circuit: cir.Circuit, + circuit_qubits: QubitSet, + global_phase: float | None, + column_strs: list, + ) -> tuple[float | None, list[str]]: + time_slices = circuit.moments.time_slices() + + # Moment columns + for time, instructions in time_slices.items(): + global_phase = _compute_moment_global_phase(global_phase, instructions) + moment_str = cls._create_diagram_column_set( + str(time), circuit_qubits, instructions, global_phase + ) + column_strs.append(moment_str) + + # Result type columns + additional_result_types, target_result_types = _categorize_result_types( + circuit.result_types + ) + if target_result_types: + column_strs.append( + cls._create_diagram_column_set( + "Result Types", circuit_qubits, target_result_types, global_phase + ) + ) + return global_phase, additional_result_types + + @classmethod + def _create_diagram_column_set( + cls, + col_title: str, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None, + ) -> str: + """Return a set of columns in the string diagram of the circuit for a list of items. + + Args: + col_title (str): title of column set + circuit_qubits (QubitSet): qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this set + + Returns: + str: A string diagram for the column set. + """ + + # Group items to separate out overlapping multi-qubit items + groupings = _group_items(circuit_qubits, items) + + column_strs = [ + cls._create_diagram_column(circuit_qubits, grouping[1], global_phase) + for grouping in groupings + ] + + # Unite column strings + lines = _unite_strings(column_strs[0], column_strs[1:]) + + # Adjust for column title width + col_title_width = len(col_title) + symbols_width = len(lines[0]) - 1 + if symbols_width < col_title_width: + diff = col_title_width - symbols_width + for i in range(len(lines) - 1): + if lines[i].endswith(cls._qubit_line_character()): + lines[i] += cls._qubit_line_character() * diff + else: + lines[i] += " " + + first_line = "{:^{width}}{vdelim}\n".format( + col_title, width=len(lines[0]) - 1, vdelim=cls._vertical_delimiter() + ) + + return first_line + "\n".join(lines) + + @classmethod + def _create_output( + cls, + symbols: dict[Qubit, str], + margins: dict[Qubit, str], + qubits: QubitSet, + global_phase: float | None, + ) -> str: + """Creates the ouput for a single column: + a. If there was one or more gphase gate, create a first line with the total global + phase shift ending with the _vertical_delimiter() class attribute, e.g. 0.14| + b. for each qubit, append the text representation produces by cls._draw_symbol + + Args: + symbols (dict[Qubit, str]): dictionary of the gate name for each qubit + margins (dict[Qubit, str]): map of the qubit interconnections. Specific to the + `_draw_symbol` classmethod. + qubits (QubitSet): set of the circuit qubits + global_phase (float | None): total global phase shift added during the moment + + Returns: + str: a string representing a diagram column. + """ + symbols_width = max([len(symbol) for symbol in symbols.values()]) + cls._box_pad() + output = "" + + if global_phase is not None: + global_phase_str = ( + f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) + ) + symbols_width = max([symbols_width, len(global_phase_str)]) + output += "{0:{fill}{align}{width}}{vdelim}\n".format( + global_phase_str, + fill=" ", + align="^", + width=symbols_width, + vdelim=cls._vertical_delimiter(), + ) + + for qubit in qubits: + output += cls._draw_symbol(symbols[qubit], symbols_width, margins[qubit]) + return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py new file mode 100644 index 000000000..9b85c30e1 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -0,0 +1,197 @@ +# 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. + +from __future__ import annotations + +from functools import reduce +from typing import Union + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType +from braket.circuits.noise import Noise +from braket.circuits.result_type import ResultType +from braket.registers.qubit_set import QubitSet + + +def _add_footers( + lines: list, + circuit: cir.Circuit, + global_phase: float | None, + additional_result_types: list[str], +) -> str: + if global_phase: + lines.append(f"\nGlobal phase: {global_phase}") + + # Additional result types line on bottom + if additional_result_types: + lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") + + # A list of parameters in the circuit to the currently assigned values. + if circuit.parameters: + lines.append( + "\nUnassigned parameters: " + f"{sorted(circuit.parameters, key=lambda param: param.name)}." + ) + + return "\n".join(lines) + + +def _prepare_qubit_identifier_column( + circuit: cir.Circuit, + circuit_qubits: QubitSet, + vdelim: str, + qubit_line_char: str, + line_spacing_before: int, + line_spacing_after: int, +) -> tuple[str, float | None]: + # Y Axis Column + y_axis_width = len(str(int(max(circuit_qubits)))) + y_axis_str = "{0:{width}} : {vdelim}\n".format("T", width=y_axis_width + 1, vdelim=vdelim) + + global_phase = None + if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + y_axis_str += "{0:{width}} : {vdelim}\n".format("GP", width=y_axis_width, vdelim=vdelim) + global_phase = 0 + + for qubit in circuit_qubits: + for _ in range(line_spacing_before): + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + + y_axis_str += "q{0:{width}} : {qubit_line_char}\n".format( + str(int(qubit)), + width=y_axis_width, + qubit_line_char=qubit_line_char, + ) + + for _ in range(line_spacing_after): + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + return y_axis_str, global_phase + + +def _unite_strings(first_column: str, column_strs: list[str]) -> list: + lines = first_column.split("\n") + for col_str in column_strs: + for i, line_in_col in enumerate(col_str.split("\n")): + lines[i] += line_in_col + return lines + + +def _compute_moment_global_phase( + global_phase: float | None, items: list[Instruction] +) -> float | None: + """ + Compute the integrated phase at a certain moment. + + Args: + global_phase (float | None): The integrated phase up to the computed moment + items (list[Instruction]): list of instructions + + Returns: + float | None: The updated integrated phase. + """ + moment_phase = 0 + for item in items: + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + moment_phase += item.operator.angle + return global_phase + moment_phase if global_phase is not None else None + + +def _group_items( + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], +) -> list[tuple[QubitSet, list[Instruction]]]: + """ + Group instructions in a moment + + Args: + circuit_qubits (QubitSet): set of qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + + Returns: + list[tuple[QubitSet, list[Instruction]]]: list of grouped instructions or result types. + """ + groupings = [] + for item in items: + # Can only print Gate and Noise operators for instructions at the moment + if isinstance(item, Instruction) and not isinstance( + item.operator, (Gate, Noise, CompilerDirective) + ): + continue + + # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range + # to an empty list and we just add it to the first group below. + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + qubit_range = QubitSet() + elif (isinstance(item, ResultType) and not item.target) or ( + isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) + ): + qubit_range = circuit_qubits + else: + if isinstance(item.target, list): + target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target = item.target + control = getattr(item, "control", QubitSet()) + target_and_control = target.union(control) + qubit_range = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + + found_grouping = False + for group in groupings: + qubits_added = group[0] + instr_group = group[1] + # Take into account overlapping multi-qubit gates + if not qubits_added.intersection(set(qubit_range)): + instr_group.append(item) + qubits_added.update(qubit_range) + found_grouping = True + break + + if not found_grouping: + groupings.append((qubit_range, [item])) + + return groupings + + +def _categorize_result_types( + result_types: list[ResultType], +) -> tuple[list[str], list[ResultType]]: + """ + Categorize result types into result types with target and those without. + + Args: + result_types (list[ResultType]): list of result types + + Returns: + tuple[list[str], list[ResultType]]: first element is a list of result types + without `target` attribute; second element is a list of result types with + `target` attribute + """ + additional_result_types = [] + target_result_types = [] + for result_type in result_types: + if hasattr(result_type, "target"): + target_result_types.append(result_type) + else: + additional_result_types.extend(result_type.ascii_symbols) + return additional_result_types, target_result_types diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py new file mode 100644 index 000000000..9e1779cb7 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -0,0 +1,283 @@ +# 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. + +from __future__ import annotations + +from functools import reduce +from typing import Literal + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram import TextCircuitDiagram +from braket.registers.qubit import Qubit +from braket.registers.qubit_set import QubitSet + + +class UnicodeCircuitDiagram(TextCircuitDiagram): + """Builds string circuit diagrams using box-drawing characters.""" + + @staticmethod + def build_diagram(circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + return UnicodeCircuitDiagram._build(circuit) + + @classmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + return "│" + + @classmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + return "─" + + @classmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + return 4 + + @classmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + return 1 + + @classmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + return 1 + + @classmethod + def _duplicate_time_at_bottom(cls, lines: list) -> None: + # Do not add a line after the circuit + # It is safe to do because the last line is empty: _qubit_line_spacing["after"] = 1 + lines[-1] = lines[0] + + @classmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Instruction | ResultType], + global_phase: float | None = None, + ) -> str: + """Return a column in the string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Instruction | ResultType]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: a string diagram for the specified moment in time for a column. + """ + symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} + connections = {qubit: "none" for qubit in circuit_qubits} + + for item in items: + ( + target_qubits, + control_qubits, + qubits, + connections, + ascii_symbols, + map_control_qubit_states, + ) = cls._build_parameters(circuit_qubits, item, connections) + + for qubit in qubits: + # Determine if the qubit is part of the item or in the middle of a + # multi qubit item. + if qubit in target_qubits: + item_qubit_index = [ + index for index, q in enumerate(target_qubits) if q == qubit + ][0] + power_string = ( + f"^{power}" + if ( + (power := getattr(item, "power", 1)) != 1 + # this has the limitation of not printing the power + # when a user has a gate genuinely named C, but + # is necessary to enable proper printing of custom + # gates with built-in control qubits + and ascii_symbols[item_qubit_index] != "C" + ) + else "" + ) + symbols[qubit] = ( + f"{ascii_symbols[item_qubit_index]}{power_string}" + if power_string + else ascii_symbols[item_qubit_index] + ) + + elif qubit in control_qubits: + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" + else: + symbols[qubit] = "┼" + + output = cls._create_output(symbols, connections, circuit_qubits, global_phase) + return output + + @classmethod + def _build_parameters( + cls, circuit_qubits: QubitSet, item: ResultType | Instruction, connections: dict[Qubit, str] + ) -> tuple: + map_control_qubit_states = {} + + if (isinstance(item, ResultType) and not item.target) or ( + isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(qubits) + cls._update_connections(qubits, connections) + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + qubits = circuit_qubits + ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) + else: + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target + control_qubits = getattr(item, "control", QubitSet()) + control_state = getattr(item, "control_state", "1" * len(control_qubits)) + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + + target_and_control = target_qubits.union(control_qubits) + qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + ascii_symbols = item.ascii_symbols + cls._update_connections(qubits, connections) + + return ( + target_qubits, + control_qubits, + qubits, + connections, + ascii_symbols, + map_control_qubit_states, + ) + + @staticmethod + def _update_connections(qubits: QubitSet, connections: dict[Qubit, str]) -> None: + if len(qubits) > 1: + connections |= {qubit: "both" for qubit in qubits[1:-1]} + connections[qubits[-1]] = "above" + connections[qubits[0]] = "below" + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + def _draw_symbol( + cls, + symbol: str, + symbols_width: int, + connection: Literal["above", "below", "both", "none"], + ) -> str: + """Create a string representing the symbol inside a box. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): specifies if a connection + will be drawn above and/or below the box. + + Returns: + str: a string representing the symbol. + """ + top = "" + bottom = "" + if symbol in ["C", "N", "SWAP"]: + if connection in ["above", "both"]: + top = _fill_symbol(cls._vertical_delimiter(), " ") + if connection in ["below", "both"]: + bottom = _fill_symbol(cls._vertical_delimiter(), " ") + new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} + # replace SWAP by x + # the size of the moment remains as if there was a box with 4 characters inside + symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) + elif symbol in ["StartVerbatim", "EndVerbatim"]: + top, symbol, bottom = cls._build_verbatim_box(symbol, connection) + elif symbol == "┼": + top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") + symbol = _fill_symbol(f"{symbol}", cls._qubit_line_character()) + elif symbol == cls._qubit_line_character(): + # We do not box when no gate is applied. + pass + else: + top, symbol, bottom = cls._build_box(symbol, connection) + + output = f"{_fill_symbol(top, ' ', symbols_width)} \n" + output += f"{_fill_symbol(symbol, cls._qubit_line_character(), symbols_width)}{cls._qubit_line_character()}\n" + output += f"{_fill_symbol(bottom, ' ', symbols_width)} \n" + return output + + @staticmethod + def _build_box( + symbol: str, connection: Literal["above", "below", "both", "none"] + ) -> tuple[str, str, str]: + top_edge_symbol = "┴" if connection in ["above", "both"] else "─" + top = f"┌─{_fill_symbol(top_edge_symbol, '─', len(symbol))}─┐" + + bottom_edge_symbol = "┬" if connection in ["below", "both"] else "─" + bottom = f"└─{_fill_symbol(bottom_edge_symbol, '─', len(symbol))}─┘" + + symbol = f"┤ {symbol} ├" + return top, symbol, bottom + + @classmethod + def _build_verbatim_box( + cls, + symbol: Literal["StartVerbatim", "EndVerbatim"], + connection: Literal["above", "below", "both", "none"], + ) -> str: + top = "" + bottom = "" + if connection == "below": + bottom = "║" + elif connection == "both": + top = bottom = "║" + symbol = "║" + elif connection == "above": + top = "║" + symbol = "╨" + top = _fill_symbol(top, " ") + symbol = _fill_symbol(symbol, cls._qubit_line_character()) + bottom = _fill_symbol(bottom, " ") + + return top, symbol, bottom + + +def _fill_symbol(symbol: str, filler: str, width: int | None = None) -> str: + return "{0:{fill}{align}{width}}".format( + symbol, + fill=filler, + align="^", + width=width if width is not None else len(symbol), + ) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 916bfb050..88e32d92f 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -26,6 +26,10 @@ from braket.pulse import Frame, Port, PulseSequence +def _assert_correct_diagram(circ, expected): + assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) + + def test_empty_circuit(): assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" @@ -787,10 +791,6 @@ def test_pulse_gate_multi_qubit_circuit(): _assert_correct_diagram(circ, expected) -def _assert_correct_diagram(circ, expected): - assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) - - def test_circuit_with_nested_target_list(): circ = ( Circuit() diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 91b19dba9..ecaad12bf 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -18,7 +18,6 @@ import braket.ir.jaqcd as jaqcd from braket.circuits import ( - AsciiCircuitDiagram, Circuit, FreeParameter, FreeParameterExpression, @@ -28,6 +27,7 @@ Observable, QubitSet, ResultType, + UnicodeCircuitDiagram, circuit, compiler_directives, gates, @@ -199,7 +199,7 @@ def test_repr_result_types(cnot_prob): def test_str(h): - expected = AsciiCircuitDiagram.build_diagram(h) + expected = UnicodeCircuitDiagram.build_diagram(h) assert str(h) == expected diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py new file mode 100644 index 000000000..96df634cb --- /dev/null +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -0,0 +1,1021 @@ +# 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. + +import numpy as np +import pytest + +from braket.circuits import ( + Circuit, + FreeParameter, + Gate, + Instruction, + Observable, + Operator, + UnicodeCircuitDiagram, +) +from braket.pulse import Frame, Port, PulseSequence + + +def _assert_correct_diagram(circ, expected): + assert UnicodeCircuitDiagram.build_diagram(circ) == "\n".join(expected) + + +def test_empty_circuit(): + assert UnicodeCircuitDiagram.build_diagram(Circuit()) == "" + + +def test_only_gphase_circuit(): + assert UnicodeCircuitDiagram.build_diagram(Circuit().gphase(0.1)) == "Global phase: 0.1" + + +def test_one_gate_one_qubit(): + circ = Circuit().h(0) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation(): + circ = Circuit().rx(angle=3.14, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌──────────┐ ", + "q0 : ─┤ Rx(3.14) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_parameter(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌───────────┐ ", + "q0 : ─┤ Rx(theta) ├─", + " └───────────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [theta].", + ) + _assert_correct_diagram(circ, expected) + + +@pytest.mark.parametrize("target", [0, 1]) +def test_one_gate_with_global_phase(target): + circ = Circuit().x(target=target).gphase(0.15) + expected = ( + "T : │ 0 │ 1 │", + "GP : │ 0 │0.15 │", + " ┌───┐ ", + f"q{target} : ─┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + "", + "Global phase: 0.15", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_zero_global_phase(): + circ = Circuit().gphase(-0.15).x(target=0).gphase(0.15) + expected = ( + "T : │ 0 │ 1 │", + "GP : │-0.15│0.00 │", + " ┌───┐ ", + "q0 : ─┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_unicode(): + theta = FreeParameter("\u03B8") + circ = Circuit().rx(angle=theta, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌───────┐ ", + "q0 : ─┤ Rx(θ) ├─", + " └───────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_parametric_expression_global_phase_(): + theta = FreeParameter("\u03B8") + circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) + expected = ( + "T : │ 0 │ 1 │ 2 │", + "GP : │ 0 │ 2*θ │2*θ + 1.0│", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ X ├─┤ X ├───────────", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │", + "", + "Global phase: 2*θ + 1.0", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_parameter_assigned(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + new_circ = circ.make_bound_circuit({"theta": np.pi}) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌──────────┐ ", + "q0 : ─┤ Rx(3.14) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(new_circ, expected) + + +def test_qubit_width(): + circ = Circuit().h(0).h(100) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q100 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_different_size_boxes(): + circ = Circuit().cnot(0, 1).rx(2, 0.3) + expected = ( + "T : │ 0 │", + " ", + "q0 : ──────●───────", + " │ ", + " ┌─┴─┐ ", + "q1 : ────┤ X ├─────", + " └───┘ ", + " ┌──────────┐ ", + "q2 : ─┤ Rx(0.30) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_swap(): + circ = Circuit().swap(0, 2).x(1) + expected = ( + "T : │ 0 │", + " ", + "q0 : ────x───────────", + " │ ", + " │ ┌───┐ ", + "q1 : ────┼─────┤ X ├─", + " │ └───┘ ", + " │ ", + "q2 : ────x───────────", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_gate_width(): + class Foo(Gate): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["FOO"]) + + def to_ir(self, target): + return "foo" + + circ = Circuit().h(0).h(1).add_instruction(Instruction(Foo(), 0)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌─────┐ ", + "q0 : ─┤ H ├─┤ FOO ├─", + " └───┘ └─────┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├─────────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_time_width(): + circ = Circuit() + num_qubits = 8 + for qubit in range(num_qubits): + if qubit == num_qubits - 1: + break + circ.cnot(qubit, qubit + 1) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ", + "q0 : ───●───────────────────────────────────────", + " │ ", + " ┌─┴─┐ ", + "q1 : ─┤ X ├───●─────────────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q2 : ───────┤ X ├───●───────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q3 : ─────────────┤ X ├───●─────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q4 : ───────────────────┤ X ├───●───────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q5 : ─────────────────────────┤ X ├───●─────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q6 : ───────────────────────────────┤ X ├───●───", + " └───┘ │ ", + " ┌─┴─┐ ", + "q7 : ─────────────────────────────────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_two_qubits(): + circ = Circuit().cnot(4, 3).h(range(2, 6)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ┌───┐ ", + "q3 : ─┤ X ├─┤ H ├─", + " └─┬─┘ └───┘ ", + " │ ┌───┐ ", + "q4 : ───●───┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q5 : ─┤ H ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_neg_control_qubits(): + circ = Circuit().x(1, control=[0, 2], control_state=[0, 1]) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───◯───", + " │ ", + " ┌─┴─┐ ", + "q1 : ─┤ X ├─", + " └─┬─┘ ", + " │ ", + "q2 : ───●───", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_only_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=0) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───◯───", + " │ ", + " │ ", + "q1 : ───◯───", + " │ ", + " ┌─┴─┐ ", + "q2 : ─┤ X ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_three_qubits(): + circ = Circuit().x(control=(3, 4), target=5).h(range(2, 6)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ", + "q3 : ───●───┤ H ├─", + " │ └───┘ ", + " │ ┌───┐ ", + "q4 : ───●───┤ H ├─", + " │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q5 : ─┤ X ├─┤ H ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_overlapping_qubits(): + circ = Circuit().cnot(0, 2).x(control=1, target=3).h(0) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ───●─────────┤ H ├─", + " │ └───┘ ", + " │ ", + "q1 : ───┼─────●─────────", + " │ │ ", + " ┌─┴─┐ │ ", + "q2 : ─┤ X ├───┼─────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q3 : ───────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_overlapping_qubits_angled_gates(): + circ = Circuit().zz(0, 2, 0.15).x(control=1, target=3).h(0) + expected = ( + "T : │ 0 │ 1 │", + " ┌──────────┐ ┌───┐ ", + "q0 : ─┤ ZZ(0.15) ├───────┤ H ├─", + " └────┬─────┘ └───┘ ", + " │ ", + "q1 : ──────┼─────────●─────────", + " │ │ ", + " ┌────┴─────┐ │ ", + "q2 : ─┤ ZZ(0.15) ├───┼─────────", + " └──────────┘ │ ", + " ┌─┴─┐ ", + "q3 : ──────────────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_gt_two_qubits(): + circ = Circuit().h(4).x(control=3, target=5).h(4).h(2) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├─────────────", + " └───┘ ", + " ", + "q3 : ─────────●─────────", + " │ ", + " ┌───┐ │ ┌───┐ ", + "q4 : ─┤ H ├───┼───┤ H ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q5 : ───────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_non_used_qubits(): + circ = Circuit().h(4).cnot(3, 100).h(4).h(101) + expected = ( + "T : │ 0 │ 1 │", + " ", + "q3 : ─────────●─────────", + " │ ", + " ┌───┐ │ ┌───┐ ", + "q4 : ─┤ H ├───┼───┤ H ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q100 : ───────┤ X ├───────", + " └───┘ ", + " ┌───┐ ", + "q101 : ─┤ H ├─────────────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0)) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───EndVerbatim───", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───EndVerbatim───", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───EndVerbatim───┤ H ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●─────EndVerbatim───", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────╨───────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0).cnot(0, 1)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───●─────EndVerbatim───", + " └───┘ ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ───────────────╨───────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●─────EndVerbatim───┤ H ├─", + " ║ └───┘ │ ║ └───┘ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────╨───────────────┤ X ├────────╨──────────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●───────────EndVerbatim───", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────║───────────────┤ X ├───●──────────║────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ─────────╨─────────────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───●───────────EndVerbatim───", + " └───┘ ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ───────────────║───────────────┤ X ├───●──────────║────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ───────────────╨─────────────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●───────────EndVerbatim───┤ H ├─", + " ║ └───┘ │ ║ └───┘ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────║───────────────┤ X ├───●──────────║──────────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ─────────╨─────────────────────┤ X ├────────╨──────────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_different_qubits(): + circ = Circuit().h(1).add_verbatim_box(Circuit().h(0)).cnot(3, 4) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ", + "q0 : ─────────StartVerbatim───┤ H ├───EndVerbatim─────────", + " ║ └───┘ ║ ", + " ┌───┐ ║ ║ ", + "q1 : ─┤ H ├─────────║──────────────────────║──────────────", + " └───┘ ║ ║ ", + " ║ ║ ", + "q3 : ───────────────║──────────────────────║──────────●───", + " ║ ║ │ ", + " ║ ║ ┌─┴─┐ ", + "q4 : ───────────────╨──────────────────────╨────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_qubset_qubits(): + circ = Circuit().h(1).cnot(0, 1).cnot(1, 2).add_verbatim_box(Circuit().h(1)).cnot(2, 3) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ", + "q0 : ─────────●───────────StartVerbatim───────────EndVerbatim─────────", + " │ ║ ║ ", + " ┌───┐ ┌─┴─┐ ║ ┌───┐ ║ ", + "q1 : ─┤ H ├─┤ X ├───●───────────║─────────┤ H ├────────║──────────────", + " └───┘ └───┘ │ ║ └───┘ ║ ", + " ┌─┴─┐ ║ ║ ", + "q2 : ─────────────┤ X ├─────────║──────────────────────║──────────●───", + " └───┘ ║ ║ │ ", + " ║ ║ ┌─┴─┐ ", + "q3 : ───────────────────────────╨──────────────────────╨────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_ignore_non_gates(): + class Foo(Operator): + @property + def name(self) -> str: + return "foo" + + def to_ir(self, target): + return "foo" + + circ = Circuit().h(0).h(1).cnot(1, 2).add_instruction(Instruction(Foo(), 0)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├───●───", + " └───┘ │ ", + " ┌─┴─┐ ", + "q2 : ───────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_single_qubit_result_types_target_none(): + circ = Circuit().h(0).probability() + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ─┤ H ├─┤ Probability ├─", + " └───┘ └─────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_result_types_target_none(): + circ = Circuit().h(0).h(100).probability() + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ─┤ H ├─┤ Probability ├─", + " └───┘ └──────┬──────┘ ", + " ┌───┐ ┌──────┴──────┐ ", + "q100 : ─┤ H ├─┤ Probability ├─", + " └───┘ └─────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_result_types_target_some(): + circ = ( + Circuit() + .h(0) + .h(1) + .h(100) + .expectation(observable=Observable.Y() @ Observable.Z(), target=[0, 100]) + ) + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌──────────────────┐ ", + "q0 : ─┤ H ├─┤ Expectation(Y@Z) ├─", + " └───┘ └────────┬─────────┘ ", + " ┌───┐ │ ", + "q1 : ─┤ H ├──────────┼───────────", + " └───┘ │ ", + " ┌───┐ ┌────────┴─────────┐ ", + "q100 : ─┤ H ├─┤ Expectation(Y@Z) ├─", + " └───┘ └──────────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_additional_result_types(): + circ = Circuit().h(0).h(1).h(100).state_vector().amplitude(["110", "001"]) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q100 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + "", + "Additional result types: StateVector, Amplitude(110,001)", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types(): + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=2) + .sample(observable=Observable.Y()) + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ┌───────────┐ ", + "q0 : ───●─────────┤ H ├──┤ Variance(Y) ├───┤ Sample(Y) ├─", + " │ └───┘ └─────────────┘ └─────┬─────┘ ", + " │ ┌─────┴─────┐ ", + "q1 : ───┼─────●────────────────────────────┤ Sample(Y) ├─", + " │ │ └─────┬─────┘ ", + " ┌─┴─┐ │ ┌────────────────┐ ┌─────┴─────┐ ", + "q2 : ─┤ X ├───┼─────────┤ Expectation(Y) ├─┤ Sample(Y) ├─", + " └───┘ │ └────────────────┘ └─────┬─────┘ ", + " ┌─┴─┐ ┌─────┴─────┐ ", + "q3 : ───────┤ X ├──────────────────────────┤ Sample(Y) ├─", + " └───┘ └───────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types_with_state_vector_amplitude(): + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=3) + .expectation(observable=Observable.Hermitian(np.array([[1.0, 0.0], [0.0, 1.0]])), target=1) + .amplitude(["0001"]) + .state_vector() + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ───●─────────┤ H ├──────┤ Variance(Y) ├───────", + " │ └───┘ └─────────────┘ ", + " │ ┌────────────────────────┐ ", + "q1 : ───┼─────●─────────┤ Expectation(Hermitian) ├─", + " │ │ └────────────────────────┘ ", + " ┌─┴─┐ │ ", + "q2 : ─┤ X ├───┼────────────────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ┌────────────────┐ ", + "q3 : ───────┤ X ├───────────┤ Expectation(Y) ├─────", + " └───┘ └────────────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + "", + "Additional result types: Amplitude(0001), StateVector", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): + herm_matrix = (Observable.Y() @ Observable.Z()).to_matrix() + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=3) + .expectation( + observable=Observable.Hermitian( + matrix=herm_matrix, + display_name="MyHerm", + ), + target=[1, 2], + ) + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ───●─────────┤ H ├─────┤ Variance(Y) ├─────", + " │ └───┘ └─────────────┘ ", + " │ ┌─────────────────────┐ ", + "q1 : ───┼─────●─────────┤ Expectation(MyHerm) ├─", + " │ │ └──────────┬──────────┘ ", + " ┌─┴─┐ │ ┌──────────┴──────────┐ ", + "q2 : ─┤ X ├───┼─────────┤ Expectation(MyHerm) ├─", + " └───┘ │ └─────────────────────┘ ", + " ┌─┴─┐ ┌────────────────┐ ", + "q3 : ───────┤ X ├─────────┤ Expectation(Y) ├────", + " └───┘ └────────────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_1qubit(): + circ = Circuit().h(0).x(1).bit_flip(1, 0.1) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─────────────", + " └───┘ ", + " ┌───┐ ┌─────────┐ ", + "q1 : ─┤ X ├─┤ BF(0.1) ├─", + " └───┘ └─────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_2qubit(): + circ = Circuit().h(1).kraus((0, 2), [np.eye(4)]) + expected = ( + "T : │ 0 │", + " ┌────┐ ", + "q0 : ───────┤ KR ├─", + " └─┬──┘ ", + " ┌───┐ │ ", + "q1 : ─┤ H ├───┼────", + " └───┘ │ ", + " ┌─┴──┐ ", + "q2 : ───────┤ KR ├─", + " └────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_multi_probabilities(): + circ = Circuit().h(0).x(1).pauli_channel(1, 0.1, 0.2, 0.3) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─────────────────────", + " └───┘ ", + " ┌───┐ ┌─────────────────┐ ", + "q1 : ─┤ X ├─┤ PC(0.1,0.2,0.3) ├─", + " └───┘ └─────────────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_multi_probabilities_with_parameter(): + a = FreeParameter("a") + b = FreeParameter("b") + c = FreeParameter("c") + circ = Circuit().h(0).x(1).pauli_channel(1, a, b, c) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├───────────────", + " └───┘ ", + " ┌───┐ ┌───────────┐ ", + "q1 : ─┤ X ├─┤ PC(a,b,c) ├─", + " └───┘ └───────────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [a, b, c].", + ) + _assert_correct_diagram(circ, expected) + + +def test_pulse_gate_1_qubit_circuit(): + circ = ( + Circuit() + .h(0) + .pulse_gate(0, PulseSequence().set_phase(Frame("x", Port("px", 1e-9), 1e9, 0), 0)) + ) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌────┐ ", + "q0 : ─┤ H ├─┤ PG ├─", + " └───┘ └────┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_pulse_gate_multi_qubit_circuit(): + circ = ( + Circuit() + .h(0) + .pulse_gate([0, 1], PulseSequence().set_phase(Frame("x", Port("px", 1e-9), 1e9, 0), 0)) + ) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌────┐ ", + "q0 : ─┤ H ├─┤ PG ├─", + " └───┘ └─┬──┘ ", + " ┌─┴──┐ ", + "q1 : ───────┤ PG ├─", + " └────┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_circuit_with_nested_target_list(): + circ = ( + Circuit() + .h(0) + .h(1) + .expectation( + observable=(2 * Observable.Y()) @ (-3 * Observable.I()) + - 0.75 * Observable.Y() @ Observable.Z(), + target=[[0, 1], [0, 1]], + ) + ) + + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌──────────────────────────┐ ", + "q0 : ─┤ H ├─┤ Expectation(Hamiltonian) ├─", + " └───┘ └────────────┬─────────────┘ ", + " ┌───┐ ┌────────────┴─────────────┐ ", + "q1 : ─┤ H ├─┤ Expectation(Hamiltonian) ├─", + " └───┘ └──────────────────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_hamiltonian(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .rx(0, FreeParameter("theta")) + .adjoint_gradient( + 4 * (2e-5 * Observable.Z() + 2 * (3 * Observable.X() @ (2 * Observable.Y()))), + [[0], [1, 2]], + ) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │ Result Types │", + " ┌───┐ ┌───────────┐ ┌──────────────────────────────┐ ", + "q0 : ─┤ H ├───●───┤ Rx(theta) ├─┤ AdjointGradient(Hamiltonian) ├─", + " └───┘ │ └───────────┘ └──────────────┬───────────────┘ ", + " ┌─┴─┐ ┌──────────────┴───────────────┐ ", + "q1 : ───────┤ X ├───────────────┤ AdjointGradient(Hamiltonian) ├─", + " └───┘ └──────────────┬───────────────┘ ", + " ┌──────────────┴───────────────┐ ", + "q2 : ───────────────────────────┤ AdjointGradient(Hamiltonian) ├─", + " └──────────────────────────────┘ ", + "T : │ 0 │ 1 │ 2 │ Result Types │", + "", + "Unassigned parameters: [theta].", + ) + _assert_correct_diagram(circ, expected) + + +def test_power(): + class Foo(Gate): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["FOO"]) + + class CFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["C", "FOO"]) + + class FooFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["FOO", "FOO"]) + + circ = Circuit().h(0, power=1).h(1, power=0).h(2, power=-3.14) + circ.add_instruction(Instruction(Foo(), 0, power=-1)) + circ.add_instruction(Instruction(CFoo(), (0, 1), power=2)) + circ.add_instruction(Instruction(CFoo(), (1, 2), control=0, power=3)) + circ.add_instruction(Instruction(FooFoo(), (1, 3), control=[0, 2], power=4)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌────────┐ ", + "q0 : ────┤ H ├────┤ FOO^-1 ├─────●─────────●─────────●─────", + " └───┘ └────────┘ │ │ │ ", + " ┌─────┐ ┌───┴───┐ │ ┌───┴───┐ ", + "q1 : ───┤ H^0 ├──────────────┤ FOO^2 ├─────●─────┤ FOO^4 ├─", + " └─────┘ └───────┘ │ └───┬───┘ ", + " ┌─────────┐ ┌───┴───┐ │ ", + "q2 : ─┤ H^-3.14 ├──────────────────────┤ FOO^3 ├─────●─────", + " └─────────┘ └───────┘ │ ", + " ┌───┴───┐ ", + "q3 : ────────────────────────────────────────────┤ FOO^4 ├─", + " └───────┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_unbalanced_ascii_symbols(): + class FooFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["FOOO", "FOO"]) + + circ = Circuit().add_instruction(Instruction(FooFoo(), (1, 3), control=[0, 2], power=4)) + expected = ( + "T : │ 0 │", + " ", + "q0 : ─────●──────", + " │ ", + " ┌───┴────┐ ", + "q1 : ─┤ FOOO^4 ├─", + " └───┬────┘ ", + " │ ", + "q2 : ─────●──────", + " │ ", + " ┌───┴───┐ ", + "q3 : ─┤ FOO^4 ├──", + " └───────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) From da081b754bec8e27e9eb54b263f689d65a66bc05 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Mar 2024 16:13:47 +0000 Subject: [PATCH 084/347] prepare release v1.73.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d625ca2..150c88ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.0 (2024-03-07) + +### Features + + * update circuit drawing + ## v1.72.2 (2024-03-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b77e9d94c..68f5f1a4c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.3.dev0" +__version__ = "1.73.0" From b4955aa4ac57d07c89f509aa14fad6aa65bc718e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Mar 2024 16:13:47 +0000 Subject: [PATCH 085/347] update development version to v1.73.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 68f5f1a4c..52d1e573d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.0" +__version__ = "1.73.1.dev0" From 5deeac3e8506e1d5eccc90ccbf53c7071415c975 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:53:42 -0800 Subject: [PATCH 086/347] fix: allow for braket endpoint to be set within the jobs (#902) * fix: allow for braket endpoint to be set within the jobs * Update test/integ_tests/conftest.py Co-authored-by: Cody Wang --------- Co-authored-by: Coull Co-authored-by: Cody Wang --- test/integ_tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 2a56d8074..b8d0d43df 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -36,6 +36,8 @@ def pytest_configure_node(node): """xdist hook""" node.workerinput["JOB_COMPLETED_NAME"] = job_complete_name node.workerinput["JOB_FAILED_NAME"] = job_fail_name + if endpoint := os.getenv("BRAKET_ENDPOINT"): + node.workerinput["BRAKET_ENDPOINT"] = endpoint def pytest_xdist_node_collection_finished(ids): From 1057756824a9e914f9cf65ddacf52ea11475b994 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 11 Mar 2024 16:17:24 +0000 Subject: [PATCH 087/347] prepare release v1.73.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 150c88ddd..1040afb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.1 (2024-03-11) + +### Bug Fixes and Other Changes + + * allow for braket endpoint to be set within the jobs + ## v1.73.0 (2024-03-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 52d1e573d..d43c3860c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.1.dev0" +__version__ = "1.73.1" From 1106c6cbd13e66eaf2e299e9f4c413772c6276f2 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 11 Mar 2024 16:17:24 +0000 Subject: [PATCH 088/347] update development version to v1.73.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d43c3860c..ba58da846 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.1" +__version__ = "1.73.2.dev0" From 9811b9b17a3b550989ddc73fb51c779a2f304e00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:32:33 -0700 Subject: [PATCH 089/347] infra: bump codecov/codecov-action from 4.0.1 to 4.1.0 (#906) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/e0b68c6749509c5f83f984dd99a76a1c1a231044...54bcd8715eee62d40e33596ef5e8f0f48dbbccab) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 516f93c70..f4ac4e916 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 + uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 40219cd359716a106a3e67351e8660b652682a3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:39:42 -0700 Subject: [PATCH 090/347] infra: bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 (#907) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.12 to 1.8.14. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/e53eb8b103ffcb59469888563dc324e3c8ba6f06...81e9d935c883d0b210363ab89cf05f3894778450) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 0ec7ff9b2..a6a359e93 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@e53eb8b103ffcb59469888563dc324e3c8ba6f06 # release/v1 + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # release/v1 with: password: ${{ secrets.pypi_token }} From 628781d6cdcea687cf41392dbc6cf920f42500b6 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:52:40 -0700 Subject: [PATCH 091/347] fix: increase tol value for our integ tests (#912) --- test/integ_tests/gate_model_device_testing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 191084145..41f20da8e 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -27,7 +27,7 @@ def get_tol(shots: int) -> Dict[str, float]: - return {"atol": 0.1, "rtol": 0.15} if shots else {"atol": 0.01, "rtol": 0} + return {"atol": 0.2, "rtol": 0.3} if shots else {"atol": 0.01, "rtol": 0} def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): From 9e3ccda6b77c67c0246448812c54d7df4dfe0cf8 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Mar 2024 21:06:57 +0000 Subject: [PATCH 092/347] prepare release v1.73.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1040afb39..a26d15ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.2 (2024-03-13) + +### Bug Fixes and Other Changes + + * increase tol value for our integ tests + ## v1.73.1 (2024-03-11) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ba58da846..3aede694e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.2.dev0" +__version__ = "1.73.2" From 22955b1d6567513e5e30709fb010352e2a83ea12 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Mar 2024 21:06:57 +0000 Subject: [PATCH 093/347] update development version to v1.73.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3aede694e..d94f8a3e0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.2" +__version__ = "1.73.3.dev0" From 5e5b2f348ae17306f1e3b5f26f21fa3548c95526 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:37:59 -0700 Subject: [PATCH 094/347] test: add in the protocol for the ssl context (#911) --- test/unit_tests/braket/jobs/test_hybrid_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index e757c6a69..592af6840 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -5,7 +5,7 @@ import tempfile from logging import getLogger from pathlib import Path -from ssl import SSLContext +from ssl import PROTOCOL_TLS_CLIENT, SSLContext from unittest.mock import MagicMock, patch import job_module @@ -488,7 +488,7 @@ def my_entry(*args): def test_serialization_error(aws_session): - ssl_context = SSLContext() + ssl_context = SSLContext(protocol=PROTOCOL_TLS_CLIENT) @hybrid_job(device=None, aws_session=aws_session) def fails_serialization(): From 6029c560ea2c287c9a381bf6926ad4beb8426181 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:06:50 -0700 Subject: [PATCH 095/347] fix: store account id if already accessed (#908) --- src/braket/aws/aws_session.py | 11 +++++++++-- test/unit_tests/braket/aws/test_aws_session.py | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index a1ae0c7bb..d2f2099f4 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -85,7 +85,6 @@ def __init__( self.braket_client = self.boto_session.client( "braket", config=self._config, endpoint_url=os.environ.get("BRAKET_ENDPOINT") ) - self._update_user_agent() self._custom_default_bucket = bool(default_bucket) self._default_bucket = default_bucket or os.environ.get("AMZN_BRAKET_OUT_S3_BUCKET") @@ -101,6 +100,7 @@ def __init__( self._sts = None self._logs = None self._ecr = None + self._account_id = None @property def region(self) -> str: @@ -108,7 +108,14 @@ def region(self) -> str: @property def account_id(self) -> str: - return self.sts_client.get_caller_identity()["Account"] + """Gets the caller's account number. + + Returns: + str: The account number of the caller. + """ + if not self._account_id: + self._account_id = self.sts_client.get_caller_identity()["Account"] + return self._account_id @property def iam_client(self) -> client: diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index c61d22606..56d23b2e9 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -58,7 +58,6 @@ def aws_session(boto_session, braket_client, account_id): _aws_session._sts.get_caller_identity.return_value = { "Account": account_id, } - _aws_session._s3 = Mock() return _aws_session @@ -998,6 +997,12 @@ def test_upload_to_s3(aws_session): aws_session._s3.upload_file.assert_called_with(filename, bucket, key) +def test_account_id_idempotency(aws_session, account_id): + acc_id = aws_session.account_id + assert acc_id == aws_session.account_id + assert acc_id == account_id + + def test_upload_local_data(aws_session): with tempfile.TemporaryDirectory() as temp_dir: os.chdir(temp_dir) From 5ab4460b3e786d4eb992018d85c78e69de2157f1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Mar 2024 16:13:23 +0000 Subject: [PATCH 096/347] prepare release v1.73.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26d15ed5..43cb66841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.3 (2024-03-18) + +### Bug Fixes and Other Changes + + * store account id if already accessed + ## v1.73.2 (2024-03-13) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d94f8a3e0..f9e668a4e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.3.dev0" +__version__ = "1.73.3" From fc624bb0c07229c3f5b4de3726e13eb04f5b9beb Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Mar 2024 16:13:23 +0000 Subject: [PATCH 097/347] update development version to v1.73.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f9e668a4e..55053a2de 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.3" +__version__ = "1.73.4.dev0" From 4e9ae7552a431ca5e2209be9eb261556113ecda0 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:05:26 -0400 Subject: [PATCH 098/347] feat: Allow sets of calibrations in batches (#859) * add duration type to FreeParameterExpression * consider FreeParameter as float * move to_ast to FreeParameterExprsesion * change back FPEIdentifier's parent to Identifier * clean up syntax * add precision about the expression type * add __repr__ to waveforms * do not simplify constants with defcals * add type validation * update oqpy to 0.3.2 * fix linters * increase test coverage * update to oqpy 0.3.3 * fix last merge commit * fix type hints * update to oqpy 0.3.4 * fix oqpy to 0.3.3 * remove FreeParameterExpressionIdentitifer * declare input parameters with pulse sequences * use machine-size types * create _InputVarSplitter * remove never visited branch * fix partial coverage * hacking test because the set order changes with python version * pass inputs with PulseSequence * pass empty dict to OpenQasmProgram * force FloatVar locally * add FreeDurationParameterExpression * simplify operation methods * use TYPE_CHECKING * move FreeDurationParameterExpression to pulse folder * remove TYPE_CHECKING * remove algebra rule for FreeDurationParameterExpression * accept integers as binding values * convert FreeParameter to OQpyExpression * fix coverage * clean tests * register freeparameters with delay * trigger GitHub actions * modify and rename _format_parameter_ast * update to oqpy 0.3.5 OQPy 0.3.5 converts now correctly ExpressionConvertible to duration * trigger GitHub actions * clean code * clean import * pass empty dict early * pass gate_definitions sets to batches * clean code * changes according to feedback * fix mock tests * avoid infinite loop * scale code * rename vars * fix merge * improve linters * Merge branch 'main' into jcjaskula-aws/gate_definitions_batch * fix merge * rename var * fix docstring * fix docstring --------- Co-authored-by: Cody Wang Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Kshitij Chhabra Co-authored-by: Aaron Berdy --- src/braket/aws/aws_quantum_task.py | 22 ++--- src/braket/aws/aws_quantum_task_batch.py | 96 +++++++++++++------ src/braket/circuits/circuit.py | 71 +++++++------- test/unit_tests/braket/aws/test_aws_device.py | 13 ++- 4 files changed, 122 insertions(+), 80 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index c6ad36b27..54f544159 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -105,7 +105,7 @@ def create( disable_qubit_rewiring: bool = False, tags: dict[str, str] | None = None, inputs: dict[str, float] | None = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, quiet: bool = False, reservation_arn: str | None = None, *args, @@ -148,10 +148,9 @@ def create( IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. - gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None): - A `Dict` for user defined gate calibration. The calibration is defined for - for a particular `Gate` on a particular `QubitSet` and is represented by - a `PulseSequence`. + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): A `dict` + of user defined gate calibrations. Each calibration is defined for a particular + `Gate` on a particular `QubitSet` and is represented by a `PulseSequence`. Default: None. quiet (bool): Sets the verbosity of the logger to low and does not report queue @@ -190,6 +189,7 @@ def create( if tags is not None: create_task_kwargs.update({"tags": tags}) inputs = inputs or {} + gate_definitions = gate_definitions or {} if reservation_arn: create_task_kwargs.update( @@ -561,7 +561,7 @@ def _create_internal( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -577,7 +577,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -600,7 +600,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -639,7 +639,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -657,7 +657,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -678,7 +678,7 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions - or gate_definitions is not None + or gate_definitions or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index a02dfa6d6..ed3430274 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -23,8 +23,11 @@ from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.circuits.gate import Gate from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse.pulse_sequence import PulseSequence +from braket.registers.qubit_set import QubitSet from braket.tasks.quantum_task_batch import QuantumTaskBatch @@ -61,6 +64,13 @@ def __init__( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] | None = None, + gate_definitions: ( + Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] + | None + ) = None, reservation_arn: str | None = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, @@ -92,6 +102,9 @@ def __init__( inputs (Union[dict[str, float], list[dict[str, float]]] | None): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. + gate_definitions (Union[dict[tuple[Gate, QubitSet], PulseSequence], list[dict[tuple[Gate, QubitSet], PulseSequence]]] | None): # noqa: E501 + User-defined gate calibration. The calibration is defined for a particular `Gate` on a + particular `QubitSet` and is represented by a `PulseSequence`. Default: None. reservation_arn (str | None): The reservation ARN provided by Braket Direct to reserve exclusive usage for the device to run the quantum task on. Note: If you are creating tasks in a job that itself was created reservation ARN, @@ -111,6 +124,7 @@ def __init__( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, @@ -134,7 +148,7 @@ def __init__( self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @staticmethod - def _tasks_and_inputs( + def _tasks_inputs_gatedefs( task_specifications: Union[ Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], list[ @@ -144,45 +158,55 @@ def _tasks_and_inputs( ], ], inputs: Union[dict[str, float], list[dict[str, float]]] = None, + gate_definitions: Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] = None, ) -> list[ tuple[ Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], dict[str, float], + dict[tuple[Gate, QubitSet], PulseSequence], ] ]: inputs = inputs or {} - - max_inputs_tasks = 1 - single_task = isinstance( - task_specifications, - (Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation), - ) - single_input = isinstance(inputs, dict) - - max_inputs_tasks = ( - max(max_inputs_tasks, len(task_specifications)) if not single_task else max_inputs_tasks - ) - max_inputs_tasks = ( - max(max_inputs_tasks, len(inputs)) if not single_input else max_inputs_tasks + gate_definitions = gate_definitions or {} + + single_task_type = ( + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + AnalogHamiltonianSimulation, ) + single_input_type = dict + single_gate_definitions_type = dict - if not single_task and not single_input: - if len(task_specifications) != len(inputs): - raise ValueError("Multiple inputs and task specifications must be equal in number.") - if single_task: - task_specifications = repeat(task_specifications, times=max_inputs_tasks) + args = [task_specifications, inputs, gate_definitions] + single_arg_types = [single_task_type, single_input_type, single_gate_definitions_type] - if single_input: - inputs = repeat(inputs, times=max_inputs_tasks) + batch_length = 1 + arg_lengths = [] + for arg, single_arg_type in zip(args, single_arg_types): + arg_length = 1 if isinstance(arg, single_arg_type) else len(arg) + arg_lengths.append(arg_length) - tasks_and_inputs = zip(task_specifications, inputs) + if arg_length != 1: + if batch_length != 1 and arg_length != batch_length: + raise ValueError( + "Multiple inputs, task specifications and gate definitions must " + "be equal in length." + ) + else: + batch_length = arg_length - if single_task and single_input: - tasks_and_inputs = list(tasks_and_inputs) + for i, arg_length in enumerate(arg_lengths): + if arg_length == 1: + args[i] = repeat(args[i], batch_length) - tasks_and_inputs = list(tasks_and_inputs) + tasks_inputs_definitions = list(zip(*args)) - for task_specification, input_map in tasks_and_inputs: + for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} unbounded_parameters = param_names - set(input_map.keys()) @@ -192,7 +216,7 @@ def _tasks_and_inputs( f"{unbounded_parameters}" ) - return tasks_and_inputs + return tasks_inputs_definitions @staticmethod def _execute( @@ -213,13 +237,22 @@ def _execute( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] = None, + gate_definitions: ( + Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] + | None + ) = None, reservation_arn: str | None = None, *args, **kwargs, ) -> list[AwsQuantumTask]: - tasks_and_inputs = AwsQuantumTaskBatch._tasks_and_inputs(task_specifications, inputs) + tasks_inputs_gatedefs = AwsQuantumTaskBatch._tasks_inputs_gatedefs( + task_specifications, inputs, gate_definitions + ) max_threads = min(max_parallel, max_workers) - remaining = [0 for _ in tasks_and_inputs] + remaining = [0 for _ in tasks_inputs_gatedefs] try: with ThreadPoolExecutor(max_workers=max_threads) as executor: task_futures = [ @@ -234,11 +267,12 @@ def _execute( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, inputs=input_map, + gate_definitions=gatedefs, reservation_arn=reservation_arn, *args, **kwargs, ) - for task, input_map in tasks_and_inputs + for task, input_map, gatedefs in tasks_inputs_gatedefs ] except KeyboardInterrupt: # If an exception is thrown before the thread pool has finished, @@ -266,6 +300,7 @@ def _create_task( shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: dict[str, float] = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, reservation_arn: str | None = None, *args, **kwargs, @@ -278,6 +313,7 @@ def _create_task( shots, poll_interval_seconds=poll_interval_seconds, inputs=inputs, + gate_definitions=gate_definitions, reservation_arn=reservation_arn, *args, **kwargs, diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 36f0e68fb..3f4918a1f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1125,6 +1125,7 @@ def to_ir( ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization properties don't correspond to the `ir_type`. """ + gate_definitions = gate_definitions or {} if ir_type == IRType.JAQCD: return self._to_jaqcd() elif ir_type == IRType.OPENQASM: @@ -1137,7 +1138,7 @@ def to_ir( ) return self._to_openqasm( serialization_properties or OpenQASMSerializationProperties(), - gate_definitions.copy() if gate_definitions is not None else None, + gate_definitions.copy(), ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1185,7 +1186,7 @@ def _to_jaqcd(self) -> JaqcdProgram: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], ) -> OpenQasmProgram: ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) openqasm_ir_type = IRType.OPENQASM @@ -1222,7 +1223,7 @@ def _to_openqasm( def _create_openqasm_header( self, serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], ) -> list[str]: ir_instructions = ["OPENQASM 3.0;"] frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) @@ -1244,7 +1245,7 @@ def _create_openqasm_header( ir_instructions.append(frame_wf_declarations) return ir_instructions - def _validate_gate_calbrations_uniqueness( + def _validate_gate_calibrations_uniqueness( self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], frames: dict[str, Frame], @@ -1277,43 +1278,41 @@ def _generate_frame_wf_defcal_declarations( frames, waveforms = self._get_frames_waveforms_from_instrs(gate_definitions) - if gate_definitions is not None: - self._validate_gate_calbrations_uniqueness(gate_definitions, frames, waveforms) + self._validate_gate_calibrations_uniqueness(gate_definitions, frames, waveforms) # Declare the frames and waveforms across all pulse sequences declarable_frames = [f for f in frames.values() if not f.is_predefined] - if declarable_frames or waveforms or gate_definitions is not None: + if declarable_frames or waveforms or gate_definitions: frame_wf_to_declare = [f._to_oqpy_expression() for f in declarable_frames] frame_wf_to_declare += [wf._to_oqpy_expression() for wf in waveforms.values()] program.declare(frame_wf_to_declare, encal=True) - if gate_definitions is not None: - for key, calibration in gate_definitions.items(): - gate, qubits = key - - # Ignoring parametric gates - # Corresponding defcals with fixed arguments have been added - # in _get_frames_waveforms_from_instrs - if isinstance(gate, Parameterizable) and any( - not isinstance(parameter, (float, int, complex)) - for parameter in gate.parameters - ): - continue - - gate_name = gate._qasm_name - arguments = gate.parameters if isinstance(gate, Parameterizable) else [] - - for param in calibration.parameters: - self._parameters.add(param) - arguments = [ - param._to_oqpy_expression() if isinstance(param, FreeParameter) else param - for param in arguments - ] - - with oqpy.defcal( - program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments - ): - program += calibration._program + for key, calibration in gate_definitions.items(): + gate, qubits = key + + # Ignoring parametric gates + # Corresponding defcals with fixed arguments have been added + # in _get_frames_waveforms_from_instrs + if isinstance(gate, Parameterizable) and any( + not isinstance(parameter, (float, int, complex)) + for parameter in gate.parameters + ): + continue + + gate_name = gate._qasm_name + arguments = gate.parameters if isinstance(gate, Parameterizable) else [] + + for param in calibration.parameters: + self._parameters.add(param) + arguments = [ + param._to_oqpy_expression() if isinstance(param, FreeParameter) else param + for param in arguments + ] + + with oqpy.defcal( + program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments + ): + program += calibration._program ast = program.to_ast(encal=False, include_externs=False) return ast_to_qasm(ast) @@ -1321,7 +1320,7 @@ def _generate_frame_wf_defcal_declarations( return None def _get_frames_waveforms_from_instrs( - self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] + self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] ) -> tuple[dict[str, Frame], dict[str, Waveform]]: from braket.circuits.gates import PulseGate @@ -1336,7 +1335,7 @@ def _get_frames_waveforms_from_instrs( _validate_uniqueness(waveforms, waveform) waveforms[waveform.id] = waveform # this will change with full parametric calibration support - elif isinstance(instruction.operator, Parameterizable) and gate_definitions is not None: + elif isinstance(instruction.operator, Parameterizable): fixed_argument_calibrations = self._add_fixed_argument_calibrations( gate_definitions, instruction ) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index cac1c1536..a85ca6eb9 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1138,7 +1138,7 @@ def test_run_param_circuit_with_reservation_arn_batch_task( 43200, 0.25, inputs, - None, + {}, reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", ) @@ -1170,6 +1170,7 @@ def test_run_param_circuit_with_inputs_batch_task( 43200, 0.25, inputs, + {}, ) @@ -1303,7 +1304,9 @@ def test_batch_circuit_with_task_and_input_mismatch( inputs = [{"beta": 0.2}, {"gamma": 0.1}, {"theta": 0.2}] circ_1 = Circuit().ry(angle=3, target=0) task_specifications = [[circ_1, single_circuit_input], openqasm_program] - wrong_number_of_inputs = "Multiple inputs and task specifications must " "be equal in number." + wrong_number_of_inputs = ( + "Multiple inputs, task specifications and gate definitions must be equal in length." + ) with pytest.raises(ValueError, match=wrong_number_of_inputs): _run_batch_and_assert( @@ -1318,6 +1321,7 @@ def test_batch_circuit_with_task_and_input_mismatch( 43200, 0.25, inputs, + {}, ) @@ -1494,7 +1498,7 @@ def test_run_with_positional_args_and_kwargs( 86400, 0.25, {}, - ["foo"], + {}, "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", None, {"bar": 1, "baz": 2}, @@ -1534,6 +1538,7 @@ def test_run_batch_no_extra( 43200, 0.25, {}, + {}, ) @@ -1560,6 +1565,7 @@ def test_run_batch_with_shots( 43200, 0.25, {}, + {}, ) @@ -1586,6 +1592,7 @@ def test_run_batch_with_max_parallel_and_kwargs( 43200, 0.25, inputs={"theta": 0.2}, + gate_definitions={}, extra_kwargs={"bar": 1, "baz": 2}, ) From 314bf5916f4de6fcc12943a52a1213118304d6d4 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:43:06 -0700 Subject: [PATCH 099/347] fix: batch tasking passing lists to single tasks (#917) --- src/braket/aws/aws_quantum_task_batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index ed3430274..4d0d06b53 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -201,7 +201,7 @@ def _tasks_inputs_gatedefs( batch_length = arg_length for i, arg_length in enumerate(arg_lengths): - if arg_length == 1: + if isinstance(args[i], (dict, single_task_type)): args[i] = repeat(args[i], batch_length) tasks_inputs_definitions = list(zip(*args)) From 1751ec96afb7bd25056d8bc6538a4d358b945125 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Mar 2024 19:32:43 +0000 Subject: [PATCH 100/347] prepare release v1.74.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43cb66841..83ac54ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.74.0 (2024-03-21) + +### Features + + * Allow sets of calibrations in batches + +### Bug Fixes and Other Changes + + * batch tasking passing lists to single tasks + ## v1.73.3 (2024-03-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 55053a2de..dc2e7fdf3 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.4.dev0" +__version__ = "1.74.0" From 661529a0c003266794ab47a7812d15f315537809 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Mar 2024 19:32:43 +0000 Subject: [PATCH 101/347] update development version to v1.74.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index dc2e7fdf3..9c443c490 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.0" +__version__ = "1.74.1.dev0" From 8618fcb4a23a1b37b09d99264c0e953792117ae9 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:20:01 -0700 Subject: [PATCH 102/347] fix: temporarily pin the schemas version (#924) --- setup.py | 2 +- tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 16f96bd31..6057b05c1 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.20.2", + "amazon-braket-schemas==1.20.2", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", diff --git a/tox.ini b/tox.ini index 98a9b30e3..7e758d02d 100644 --- a/tox.ini +++ b/tox.ini @@ -129,5 +129,4 @@ commands = [test-deps] deps = # If you need to test on a certain branch, add @ after .git - git+https://github.com/amazon-braket/amazon-braket-schemas-python.git git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git From baf2ff23f76a831b279c277e4eb280579894d6ff Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Mar 2024 21:33:12 +0000 Subject: [PATCH 103/347] prepare release v1.74.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ac54ffa..0e721a98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.74.1 (2024-03-27) + +### Bug Fixes and Other Changes + + * temporarily pin the schemas version + ## v1.74.0 (2024-03-21) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9c443c490..59f3b7900 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.1.dev0" +__version__ = "1.74.1" From 182b923259b2ca5824fee49da70aad1f487daaae Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Mar 2024 21:33:12 +0000 Subject: [PATCH 104/347] update development version to v1.74.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 59f3b7900..71a218161 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.1" +__version__ = "1.74.2.dev0" From cfbdd50862c481d7e8c36b75f9828f2afe7eb2af Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:21:52 -0700 Subject: [PATCH 105/347] fix: change schemas constraint (#927) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6057b05c1..16f96bd31 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas==1.20.2", + "amazon-braket-schemas>=1.20.2", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", From c42d46e9413ac5da12d77020df46ff4a638c3956 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:48:30 -0700 Subject: [PATCH 106/347] feat: upgrade to pydantic 2.x (#926) * Update dependent-tests.yml --------- Co-authored-by: Abe Coull --- .github/workflows/dependent-tests.yml | 2 +- model.tar.gz | Bin 0 -> 336 bytes setup.py | 2 +- .../braket/circuits/test_angled_gate.py | 2 +- .../braket/devices/test_local_simulator.py | 2 +- tox.ini | 1 + 6 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 model.tar.gz diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index aca79d599..fa49d9b08 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.10", "3.11"] dependent: - - amazon-braket-pennylane-plugin-python + - amazon-braket-schemas-python steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/model.tar.gz b/model.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..93bf6a4a03f7d08314601e2907a704651eb0b07a GIT binary patch literal 336 zcmV-W0k8faiwFP!000001MHQ}OT#c2#(Va!2svx^rcE0sc<_@AJcxpL8(AB)b4^B) z%4F<+H{HjjuuWKXsQq1x%3_`*Q35O_NgDPfvx=n;VBX>FXTDp6uL>oc|;iH ztQ*0R_tGt1vB_fzy6azFJY4nqPd6kr(*HrLP1T3Kf&b071ir@3{KvGOe~7{W?VZW5 zu+G2H+HI@b<^R(B&+yQQH|ZYJS6PVVLx9iF3@cGcKUmphq=$Bp2`9+JzZAK3G8=ep zA>m_$-z!zCY6Zn}FI2{Lo>s{h=3~(^)ykK>$jr~2DW$KH$_tfy0wi27yVa%;u4*+I ii(EN5b$EX0i)v|UY58M(0ssL2{{sNvw;Ch>3;+NyA)$Q$ literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index 16f96bd31..f2621de52 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.20.2", + "amazon-braket-schemas>=1.21.0", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 4e093e5b4..ae33d4029 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from pydantic import BaseModel +from pydantic.v1 import BaseModel from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate from braket.circuits.angled_gate import DoubleAngledGate, TripleAngledGate diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 4877a0b8f..f284b5a6f 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -18,7 +18,7 @@ from unittest.mock import Mock, patch import pytest -from pydantic import create_model # This is temporary for defining properties below +from pydantic.v1 import create_model # This is temporary for defining properties below import braket.ir as ir from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation diff --git a/tox.ini b/tox.ini index 7e758d02d..98a9b30e3 100644 --- a/tox.ini +++ b/tox.ini @@ -129,4 +129,5 @@ commands = [test-deps] deps = # If you need to test on a certain branch, add @ after .git + git+https://github.com/amazon-braket/amazon-braket-schemas-python.git git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git From a52654608374ee4da1d95b44d46f8c0541d31631 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 28 Mar 2024 00:19:15 +0000 Subject: [PATCH 107/347] prepare release v1.75.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e721a98b..b8a9aa145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.75.0 (2024-03-28) + +### Features + + * upgrade to pydantic 2.x + +### Bug Fixes and Other Changes + + * change schemas constraint + ## v1.74.1 (2024-03-27) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 71a218161..a2711b93e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.2.dev0" +__version__ = "1.75.0" From 5884b1f9debbd9e6ee5cd130858b00323b766a19 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 28 Mar 2024 00:19:15 +0000 Subject: [PATCH 108/347] update development version to v1.75.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a2711b93e..9c4e62904 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.75.0" +__version__ = "1.75.1.dev0" From d6209eeb38f9e4d9efad5f7c1c7dd9a37a5e349f Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:50:45 -0700 Subject: [PATCH 109/347] fix: restore the dependent test back to pennylane (#928) --- .github/workflows/dependent-tests.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index fa49d9b08..aca79d599 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.10", "3.11"] dependent: - - amazon-braket-schemas-python + - amazon-braket-pennylane-plugin-python steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/setup.py b/setup.py index f2621de52..6c0a9e984 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.0", - "amazon-braket-default-simulator>=1.19.1", + "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", "setuptools", "backoff", From ecf30b6d7e958f446346233d487d699a32267b3e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:59:39 -0700 Subject: [PATCH 110/347] doc: fix GPI2 gate matrix representation (#919) --- src/braket/circuits/gates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index df17c03c3..b93b2dfa7 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -3301,7 +3301,7 @@ class GPi2(AngledGate): Unitary matrix: - .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i e^{-i \phi} \\ -i e^{i \phi} & 1 \end{bmatrix}. @@ -3351,7 +3351,7 @@ def gpi2( ) -> Iterable[Instruction]: r"""IonQ GPi2 gate. - .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i e^{-i \phi} \\ -i e^{i \phi} & 1 \end{bmatrix}. From e98aead1a67c2e475e7f1be7c49a0431a2faab2e Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:28:56 -0700 Subject: [PATCH 111/347] feature: add support for OpenQASM measure on a subset of qubits (#904) * add support for openqasm measure on a subset of qubits * add tests to increase test coverage * Update circuit diagram to new format * Increase test converage * add tests for edge cases * infra: bump default simulator dependency to 1.21.0 * add integ tests * remove unecessary check on the target list * add tests for result type and verbatim edge cases * Update error messages, add diagram tests, and update measure to measure all qubits when no targets provided * fix linter type hint error in docstring * add measure support to braket_program_context and added tests for from_ir * fix type hint documentation error * Fix linter errors * Apply suggestions from code review Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> * add error if qubit already measured, update target_qubits to QubitSetInput * fix linter error * remove qubit_count and ascii_symbols from Measure * Apply suggestions from code review Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> * remove error if measure targets are outside circuit * update error message casing to be consistent, change ascii_symbol type, and combined error message for qubits already measured * add a test for measuring qubits not on the circuit * allow operations after a measure if the target is not measured * remove hanging prints * fix missed case inconsistency * fix missed case inconsistency * add comments for the different cases if a qubit is already measured * updated test to check for edge case --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/circuits/braket_program_context.py | 10 + src/braket/circuits/circuit.py | 151 ++++++++- src/braket/circuits/measure.py | 99 ++++++ src/braket/circuits/moments.py | 11 + .../text_circuit_diagram_utils.py | 5 +- .../tasks/gate_model_quantum_task_result.py | 5 - test/integ_tests/test_measure.py | 85 +++++ .../circuits/test_ascii_circuit_diagram.py | 63 ++++ .../braket/circuits/test_circuit.py | 320 ++++++++++++++++-- .../braket/circuits/test_measure.py | 100 ++++++ .../circuits/test_unicode_circuit_diagram.py | 78 +++++ .../test_gate_model_quantum_task_result.py | 15 - 12 files changed, 881 insertions(+), 61 deletions(-) create mode 100644 src/braket/circuits/measure.py create mode 100644 test/integ_tests/test_measure.py create mode 100644 test/unit_tests/braket/circuits/test_measure.py diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 46d6e3b0b..a17864ed4 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -18,6 +18,7 @@ from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary +from braket.circuits.measure import Measure from braket.circuits.noises import Kraus from braket.circuits.translations import ( BRAKET_GATES, @@ -159,3 +160,12 @@ def handle_parameter_value( return evaluated_value return FreeParameterExpression(evaluated_value) return value + + def add_measure(self, target: tuple[int]) -> None: + """Add a measure instruction to the circuit + + Args: + target (tuple[int]): the target qubits to be measured. + """ + instruction = Instruction(Measure(), list(target)) + self._circuit.add_instruction(instruction) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 3f4918a1f..61fcd38bb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -26,6 +26,7 @@ from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.moments import Moments, MomentType from braket.circuits.noise import Noise from braket.circuits.noise_helpers import ( @@ -148,6 +149,7 @@ def __init__(self, addable: AddableTypes | None = None, *args, **kwargs): self._parameters = set() self._observables_simultaneously_measurable = True self._has_compiler_directives = False + self._measure_targets = None if addable is not None: self.add(addable, *args, **kwargs) @@ -273,6 +275,7 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If a meaure instruction exists on the current circuit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) @@ -298,6 +301,12 @@ def add_result_type( if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") + if self._measure_targets: + raise ValueError( + "cannot add a result type to a circuit which already contains a " + "measure instruction." + ) + if not target_mapping and not target: # Nothing has been supplied, add result_type result_type_to_add = result_type @@ -407,6 +416,46 @@ def _add_to_qubit_observable_set(self, result_type: ResultType) -> None: if isinstance(result_type, ObservableResultType) and result_type.target: self._qubit_observable_set.update(result_type.target) + def _check_if_qubit_measured( + self, + instruction: Instruction, + target: QubitSetInput | None = None, + target_mapping: dict[QubitInput, QubitInput] | None = None, + ) -> None: + """Checks if the target qubits are measured. If the qubit is already measured + the instruction will not be added to the Circuit. + + Args: + instruction (Instruction): `Instruction` to add into `self`. + target (QubitSetInput | None): Target qubits for the + `instruction`. If a single qubit gate, an instruction is created for every index + in `target`. + Default = `None`. + target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of + qubit mappings to apply to the `instruction.target`. Key is the qubit in + `instruction.target` and the value is what the key will be changed to. + Default = `None`. + + Raises: + ValueError: If adding a gate or noise operation after a measure instruction. + """ + if ( + # check if there is a measure instruction on the target qubit + target + and target in self._measure_targets + # check if there is a measure instruction on any qubits in the target_mapping + or (target_mapping and any(targ in self._measure_targets for targ in target_mapping)) + # If no target or target_mapping is supplied, check if there is a measure + # instruction on the current instructions target qubit + or ( + instruction.target + and any(targ in self._measure_targets for targ in instruction.target) + ) + ): + raise ValueError( + "cannot add a gate or noise operation on a qubit after a measure instruction." + ) + def add_instruction( self, instruction: Instruction, @@ -431,6 +480,7 @@ def add_instruction( Raises: TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If adding a gate or noise after a measure instruction. Examples: >>> instr = Instruction(Gate.CNot(), [0, 1]) @@ -458,6 +508,10 @@ def add_instruction( if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") + # Check if there is a measure instruction on the circuit + if not isinstance(instruction.operator, Measure) and self._measure_targets: + self._check_if_qubit_measured(instruction, target, target_mapping) + if not target_mapping and not target: # Nothing has been supplied, add instruction instructions_to_add = [instruction] @@ -635,6 +689,9 @@ def add_verbatim_box( if verbatim_circuit.result_types: raise ValueError("Verbatim subcircuit is not measured and cannot have result types") + if verbatim_circuit._measure_targets: + raise ValueError("cannot measure a subcircuit inside a verbatim box.") + if verbatim_circuit.instructions: self.add_instruction(Instruction(compiler_directives.StartVerbatimBox())) for instruction in verbatim_circuit.instructions: @@ -643,6 +700,90 @@ def add_verbatim_box( self._has_compiler_directives = True return self + def _add_measure(self, target_qubits: QubitSetInput) -> None: + """Adds a measure instruction to the the circuit + + Args: + target_qubits (QubitSetInput): target qubits to measure. + """ + for idx, target in enumerate(target_qubits): + num_qubits_measured = ( + len(self._measure_targets) + if self._measure_targets and len(target_qubits) == 1 + else 0 + ) + self.add_instruction( + Instruction( + operator=Measure(index=idx + num_qubits_measured), + target=target, + ) + ) + if self._measure_targets: + self._measure_targets.append(target) + else: + self._measure_targets = [target] + + def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: + """ + Add a `measure` operator to `self` ensuring only the target qubits are measured. + + Args: + target_qubits (QubitSetInput | None): target qubits to measure. + Default=None + + Returns: + Circuit: self + + Raises: + IndexError: If `self` has no qubits. + IndexError: If target qubits are not within the range of the current circuit. + ValueError: If the current circuit contains any result types. + ValueError: If the target qubit is already measured. + + Examples: + >>> circ = Circuit.h(0).cnot(0, 1).measure([0]) + >>> circ.print(list(circ.instructions)) + [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), + Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), + Qubit(1)]), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), + Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] + """ + # check whether measuring an empty circuit + if not self.qubits: + raise IndexError("cannot measure an empty circuit.") + + if isinstance(target_qubits, int): + target_qubits = [target_qubits] + + # Check if result types are added on the circuit + if self.result_types: + raise ValueError("a circuit cannot contain both measure instructions and result types.") + + if target_qubits: + # Check if the target_qubits are already measured + if self._measure_targets and all( + target in self._measure_targets for target in target_qubits + ): + intersection = set(target_qubits) & set(self._measure_targets) + raise ValueError( + f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " + "more than once." + ) + self._add_measure(target_qubits=target_qubits) + else: + # Check if any qubits are already measured + if self._measure_targets: + intersection = set(self.qubits) & set(self._measure_targets) + raise ValueError( + f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " + "more than once." + ) + # Measure all the qubits + self._add_measure(target_qubits=self.qubits) + + return self + def apply_gate_noise( self, noise: Union[type[Noise], Iterable[type[Noise]]], @@ -1208,7 +1349,8 @@ def _to_openqasm( for result_type in self.result_types ] ) - else: + # measure all the qubits if a measure instruction is not provided + elif self._measure_targets is None: qubits = ( sorted(self.qubits) if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL @@ -1230,7 +1372,12 @@ def _create_openqasm_header( for parameter in self.parameters: ir_instructions.append(f"input float {parameter};") if not self.result_types: - ir_instructions.append(f"bit[{self.qubit_count}] b;") + bit_count = ( + len(self._measure_targets) + if self._measure_targets is not None + else self.qubit_count + ) + ir_instructions.append(f"bit[{bit_count}] b;") if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL: total_qubits = max(self.qubits).real + 1 diff --git a/src/braket/circuits/measure.py b/src/braket/circuits/measure.py new file mode 100644 index 000000000..d31555ba9 --- /dev/null +++ b/src/braket/circuits/measure.py @@ -0,0 +1,99 @@ +# 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. + +from __future__ import annotations + +from typing import Any + +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) +from braket.registers.qubit_set import QubitSet + + +class Measure(QuantumOperator): + """Class `Measure` represents a measure operation on targeted qubits""" + + def __init__(self, **kwargs): + """Inits a `Measure`. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count` + """ + super().__init__(qubit_count=1, ascii_symbols=["M"]) + self._target_index = kwargs.get("index") + + @property + def ascii_symbols(self) -> tuple[str]: + """tuple[str]: Returns the ascii symbols for the measure.""" + return self._ascii_symbols + + def to_ir( + self, + target: QubitSet | None = None, + ir_type: IRType = IRType.OPENQASM, + serialization_properties: SerializationProperties | None = None, + **kwargs, + ) -> Any: + """Returns IR object of the measure operator. + + Args: + target (QubitSet | None): target qubit(s). Defaults to None + ir_type(IRType) : The IRType to use for converting the measure object to its + IR representation. Defaults to IRType.OpenQASM. + serialization_properties (SerializationProperties | None): The serialization properties + to use while serializing the object to the IR representation. The serialization + properties supplied must correspond to the supplied `ir_type`. Defaults to None. + + Returns: + Any: IR object of the measure operator. + + Raises: + ValueError: If the supplied `ir_type` is not supported. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd() + elif ir_type == IRType.OPENQASM: + return self._to_openqasm( + target, serialization_properties or OpenQASMSerializationProperties() ** kwargs + ) + else: + raise ValueError(f"supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> Any: + """Returns the JAQCD representation of the measure.""" + raise NotImplementedError("measure instructions are not supported with JAQCD.") + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ) -> str: + """Returns the openqasm string representation of the measure.""" + target_qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] + instructions = [] + for idx, qubit in enumerate(target_qubits): + bit_index = ( + self._target_index if self._target_index and len(target_qubits) == 1 else idx + ) + instructions.append(f"b[{bit_index}] = measure {qubit};") + + return "\n".join(instructions) + + def __eq__(self, other: Measure): + return isinstance(other, Measure) + + def __repr__(self): + return self.name diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 64a3556c1..031c2f5e5 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -21,6 +21,7 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.noise import Noise from braket.registers.qubit import Qubit from braket.registers.qubit_set import QubitSet @@ -34,6 +35,7 @@ class MomentType(str, Enum): INITIALIZATION_NOISE: a initialization noise channel READOUT_NOISE: a readout noise channel COMPILER_DIRECTIVE: an instruction to the compiler, external to the quantum program itself + MEASURE: a measurement """ GATE = "gate" @@ -43,6 +45,7 @@ class MomentType(str, Enum): READOUT_NOISE = "readout_noise" COMPILER_DIRECTIVE = "compiler_directive" GLOBAL_PHASE = "global_phase" + MEASURE = "measure" class MomentsKey(NamedTuple): @@ -191,6 +194,14 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._number_gphase_in_current_moment, ) self._moments[key] = instruction + elif isinstance(operator, Measure): + qubit_range = instruction.target.union(instruction.control) + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._moments[MomentsKey(time, qubit_range, MomentType.MEASURE, noise_index)] = ( + instruction + ) + self._qubits.update(qubit_range) + self._depth = max(self._depth, time + 1) else: qubit_range = instruction.target.union(instruction.control) time = self._update_qubit_times(qubit_range) diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 9b85c30e1..83763a9ae 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -20,6 +20,7 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.moments import MomentType from braket.circuits.noise import Noise from braket.circuits.result_type import ResultType @@ -129,9 +130,9 @@ def _group_items( """ groupings = [] for item in items: - # Can only print Gate and Noise operators for instructions at the moment + # Can only print Gate, Noise and Measure operators for instructions at the moment if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective) + item.operator, (Gate, Noise, CompilerDirective, Measure) ): continue diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index dec914ba7..b64d4e009 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -286,11 +286,6 @@ def _from_object_internal_computational_basis_sampling( " the result obj", ) measured_qubits = result.measuredQubits - if len(measured_qubits) != measurements.shape[1]: - raise ValueError( - f"Measured qubits {measured_qubits} is not equivalent to number of qubits " - + f"{measurements.shape[1]} in measurements" - ) if result.resultTypes: # Jaqcd does not return anything in the resultTypes schema field since the # result types are easily parsable from the IR. However, an OpenQASM program diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py new file mode 100644 index 000000000..b7fef275b --- /dev/null +++ b/test/integ_tests/test_measure.py @@ -0,0 +1,85 @@ +# 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. + +import re + +import pytest +from botocore.exceptions import ClientError + +from braket.aws.aws_device import AwsDevice +from braket.circuits.circuit import Circuit +from braket.devices import LocalSimulator + +DEVICE = LocalSimulator() +SHOTS = 8000 + +IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" +SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" + + +@pytest.mark.parametrize("arn", [(IONQ_ARN), (SIMULATOR_ARN)]) +def test_unsupported_devices(arn): + device = AwsDevice(arn) + if device.status == "OFFLINE": + pytest.skip("Device offline") + + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) + error_string = re.escape( + "An error occurred (ValidationException) when calling the " + "CreateQuantumTask operation: Device requires all qubits in the program to be measured. " + "This may be caused by declaring non-contiguous qubits or measuring partial qubits" + ) + with pytest.raises(ClientError, match=error_string): + device.run(circ, shots=1000) + + +@pytest.mark.parametrize("sim", [("braket_sv"), ("braket_dm")]) +def test_measure_on_local_sim(sim): + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) + device = LocalSimulator(sim) + result = device.run(circ, SHOTS).result() + assert len(result.measurements[0]) == 2 + assert result.measured_qubits == [0, 1] + + +@pytest.mark.parametrize("arn", [(OQC_ARN)]) +def test_measure_on_supported_devices(arn): + device = AwsDevice(arn) + if not device.is_available: + pytest.skip("Device offline") + circ = Circuit().h(0).cnot(0, 1).measure([0]) + result = device.run(circ, SHOTS).result() + assert len(result.measurements[0]) == 1 + assert result.measured_qubits == [0] + + +@pytest.mark.parametrize( + "circuit, expected_measured_qubits", + [ + (Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 1, 3]), [0, 1, 3]), + (Circuit().h(0).measure(0), [0]), + ], +) +def test_measure_targets(circuit, expected_measured_qubits): + result = DEVICE.run(circuit, SHOTS).result() + assert result.measured_qubits == expected_measured_qubits + assert len(result.measurements[0]) == len(expected_measured_qubits) + + +def test_measure_with_noise(): + device = LocalSimulator("braket_dm") + circuit = Circuit().x(0).x(1).bit_flip(0, probability=0.1).measure(0) + result = device.run(circuit, SHOTS).result() + assert result.measured_qubits == [0] + assert len(result.measurements[0]) == 1 diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 88e32d92f..7724d9077 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -872,3 +872,66 @@ def __init__(self): "T : | 0 | 1 | 2 | 3 | 4 |", ) _assert_correct_diagram(circ, expected) + + +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).measure([0]) + expected = ( + "T : |0|1|2|", + " ", + "q0 : -H-C-M-", + " | ", + "q1 : ---X---", + "", + "T : |0|1|2|", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_targets(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2, 3]) + expected = ( + "T : |0|1|2|3|4|", + " ", + "q0 : -H-C-----M-", + " | ", + "q1 : ---X-C-----", + " | ", + "q2 : -----X-C-M-", + " | ", + "q3 : -------X-M-", + "", + "T : |0|1|2|3|4|", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_instructions_after(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .cnot(2, 3) + .measure(0) + .measure(1) + .h(3) + .cnot(3, 4) + .measure([2, 3]) + ) + expected = ( + "T : |0|1|2|3|4|5|6|", + " ", + "q0 : -H-C-----M-----", + " | ", + "q1 : ---X-C---M-----", + " | ", + "q2 : -----X-C-----M-", + " | ", + "q3 : -------X-H-C-M-", + " | ", + "q4 : -----------X---", + "", + "T : |0|1|2|3|4|5|6|", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index ecaad12bf..9b34d6629 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -35,6 +35,8 @@ observables, ) from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.measure import Measure +from braket.circuits.noises import BitFlip from braket.circuits.parameterizable import Parameterizable from braket.circuits.serialization import ( IRType, @@ -570,6 +572,233 @@ def test_add_verbatim_box_result_types(): ) +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).measure([0]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_int(): + circ = Circuit().h(0).cnot(0, 1).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_multiple_targets(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 1, 3]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.CNot(), [1, 2])) + .add_instruction(Instruction(Gate.CNot(), [2, 3])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 3)) + ) + assert circ == expected + assert circ._measure_targets == [0, 1, 3] + + +def test_measure_with_noise(): + circ = Circuit().x(0).x(1).bit_flip(0, probability=0.1).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(BitFlip(probability=0.1), 0)) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_verbatim_box(): + circ = Circuit().add_verbatim_box(Circuit().x(0).x(1)).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + .add_instruction(Instruction(Measure(), 0)) + ) + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[2] q;", + "#pragma braket verbatim", + "box{", + "x q[0];", + "x q[1];", + "}", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ) + assert circ == expected + assert circ.to_ir("OPENQASM") == expected_ir + + +def test_measure_in_verbatim_subcircuit(): + message = "cannot measure a subcircuit inside a verbatim box." + with pytest.raises(ValueError, match=message): + Circuit().add_verbatim_box(Circuit().x(0).x(1).measure(0)) + + +def test_measure_qubits_out_of_range(): + circ = Circuit().h(0).cnot(0, 1).measure(4) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 4)) + ) + assert circ == expected + + +def test_measure_empty_circuit(): + with pytest.raises(IndexError): + Circuit().measure() + + +def test_measure_no_target(): + circ = Circuit().h(0).cnot(0, 1).measure() + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + ) + assert circ == expected + + +def test_measure_with_result_types(): + message = "a circuit cannot contain both measure instructions and result types." + with pytest.raises(ValueError, match=message): + Circuit().h(0).sample(observable=Observable.Z(), target=0).measure(0) + + +def test_result_type_with_measure(): + message = "cannot add a result type to a circuit which already contains a measure instruction." + with pytest.raises(ValueError, match=message): + Circuit().h(0).measure(0).sample(observable=Observable.Z(), target=0) + + +def test_measure_with_multiple_measures(): + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]).measure(2) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.H(), 2)) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 2)) + ) + assert circ == expected + + +def test_measure_same_qubit_twice(): + message = "cannot measure the same qubit\\(s\\) 0 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) + + +def test_measure_empty_measure_after_measure_with_targets(): + message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(1).measure() + + +def test_measure_gate_after(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + with pytest.raises(ValueError, match=message): + Circuit().h(0).measure(0).h([0, 1]) + + +def test_measure_gate_after_with_target_mapping(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction( + instr, target_mapping={0: 10, 1: 11} + ) + + +def test_measure_gate_after_with_target(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction(instr, target=[10, 11]) + + +def test_measure_gate_after_measurement(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).h(2) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.CNot(), [1, 2])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Gate.H(), 2)) + ) + assert circ == expected + + +def test_to_ir_with_measure(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 2]) + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ] + ), + inputs={}, + ) + assert circ.to_ir("OPENQASM") == expected_ir + + +def test_from_ir_with_measure(): + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ] + ), + inputs={}, + ) + expected_circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(2) + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ + + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) @@ -1268,7 +1497,7 @@ def foo( "expected_circuit, ir", [ ( - Circuit().h(0, control=1, control_state=0), + Circuit().h(0, control=1, control_state=0).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1284,7 +1513,7 @@ def foo( ), ), ( - Circuit().cnot(target=0, control=1), + Circuit().cnot(target=0, control=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1300,7 +1529,7 @@ def foo( ), ), ( - Circuit().x(0, control=[1], control_state=[0]), + Circuit().x(0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1316,7 +1545,7 @@ def foo( ), ), ( - Circuit().rx(0, 0.15, control=1, control_state=1), + Circuit().rx(0, 0.15, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1332,7 +1561,7 @@ def foo( ), ), ( - Circuit().ry(0, 0.2, control=1, control_state=1), + Circuit().ry(0, 0.2, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1348,7 +1577,7 @@ def foo( ), ), ( - Circuit().rz(0, 0.25, control=[1], control_state=[0]), + Circuit().rz(0, 0.25, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1364,7 +1593,7 @@ def foo( ), ), ( - Circuit().s(target=0, control=[1], control_state=[0]), + Circuit().s(target=0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1380,7 +1609,7 @@ def foo( ), ), ( - Circuit().t(target=1, control=[0], control_state=[0]), + Circuit().t(target=1, control=[0], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1396,7 +1625,7 @@ def foo( ), ), ( - Circuit().cphaseshift(target=0, control=1, angle=0.15), + Circuit().cphaseshift(target=0, control=1, angle=0.15).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1412,7 +1641,7 @@ def foo( ), ), ( - Circuit().ccnot(*[0, 1], target=2), + Circuit().ccnot(*[0, 1], target=2).measure(0).measure(1).measure(2), OpenQasmProgram( source="\n".join( [ @@ -1499,7 +1728,7 @@ def foo( ), ), ( - Circuit().bit_flip(0, 0.1), + Circuit().bit_flip(0, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1514,7 +1743,7 @@ def foo( ), ), ( - Circuit().generalized_amplitude_damping(0, 0.1, 0.1), + Circuit().generalized_amplitude_damping(0, 0.1, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1529,7 +1758,7 @@ def foo( ), ), ( - Circuit().phase_flip(0, 0.2), + Circuit().phase_flip(0, 0.2).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1544,7 +1773,7 @@ def foo( ), ), ( - Circuit().depolarizing(0, 0.5), + Circuit().depolarizing(0, 0.5).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1559,7 +1788,7 @@ def foo( ), ), ( - Circuit().amplitude_damping(0, 0.8), + Circuit().amplitude_damping(0, 0.8).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1574,7 +1803,7 @@ def foo( ), ), ( - Circuit().phase_damping(0, 0.1), + Circuit().phase_damping(0, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1606,7 +1835,12 @@ def foo( Circuit() .rx(0, 0.15, control=2, control_state=0) .rx(1, 0.3, control=[2, 3]) - .cnot(target=0, control=[2, 3, 4]), + .cnot(target=0, control=[2, 3, 4]) + .measure(0) + .measure(1) + .measure(2) + .measure(3) + .measure(4), OpenQasmProgram( source="\n".join( [ @@ -1627,7 +1861,17 @@ def foo( ), ), ( - Circuit().cnot(0, 1).cnot(target=2, control=3).cnot(target=4, control=[5, 6]), + Circuit() + .cnot(0, 1) + .cnot(target=2, control=3) + .cnot(target=4, control=[5, 6]) + .measure(0) + .measure(1) + .measure(2) + .measure(3) + .measure(4) + .measure(5) + .measure(6), OpenQasmProgram( source="\n".join( [ @@ -1650,7 +1894,7 @@ def foo( ), ), ( - Circuit().h(0, power=-2.5).h(0, power=0), + Circuit().h(0, power=-2.5).h(0, power=0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1666,7 +1910,7 @@ def foo( ), ), ( - Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]), + Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1681,7 +1925,7 @@ def foo( ), ), ( - Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3), + Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1696,7 +1940,7 @@ def foo( ), ), ( - Circuit().two_qubit_depolarizing(0, 1, probability=0.1), + Circuit().two_qubit_depolarizing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1712,7 +1956,7 @@ def foo( ), ), ( - Circuit().two_qubit_dephasing(0, 1, probability=0.1), + Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1728,7 +1972,7 @@ def foo( ), ), ( - Circuit().two_qubit_dephasing(0, 1, probability=0.1), + Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1787,13 +2031,15 @@ def foo( ), ), ( - Circuit().kraus( + Circuit() + .kraus( [0], matrices=[ np.array([[0.9486833j, 0], [0, 0.9486833j]]), np.array([[0, 0.31622777], [0.31622777, 0]]), ], - ), + ) + .measure(0), OpenQasmProgram( source="\n".join( [ @@ -1810,7 +2056,7 @@ def foo( ), ), ( - Circuit().rx(0, FreeParameter("theta")), + Circuit().rx(0, FreeParameter("theta")).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1826,7 +2072,7 @@ def foo( ), ), ( - Circuit().rx(0, np.pi), + Circuit().rx(0, np.pi).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1841,7 +2087,7 @@ def foo( ), ), ( - Circuit().rx(0, 2 * np.pi), + Circuit().rx(0, 2 * np.pi).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1856,7 +2102,7 @@ def foo( ), ), ( - Circuit().gphase(0.15).x(0), + Circuit().gphase(0.15).x(0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1879,7 +2125,7 @@ def test_from_ir(expected_circuit, ir): def test_from_ir_inputs_updated(): - circuit = Circuit().rx(0, 0.2).ry(0, 0.1) + circuit = Circuit().rx(0, 0.2).ry(0, 0.1).measure(0) openqasm = OpenQasmProgram( source="\n".join( [ @@ -1902,7 +2148,7 @@ def test_from_ir_inputs_updated(): "expected_circuit, ir", [ ( - Circuit().h(0).cnot(0, 1), + Circuit().h(0).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1922,7 +2168,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1), + Circuit().h(0).h(1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1942,7 +2188,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1).cnot(0, 1), + Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1961,7 +2207,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1).cnot(0, 1), + Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1980,7 +2226,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().x(0), + Circuit().x(0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1998,7 +2244,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")), + Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")).measure(0), OpenQasmProgram( source="\n".join( [ diff --git a/test/unit_tests/braket/circuits/test_measure.py b/test/unit_tests/braket/circuits/test_measure.py new file mode 100644 index 000000000..8911da5e4 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_measure.py @@ -0,0 +1,100 @@ +# 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. + +import pytest + +from braket.circuits.measure import Measure +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) + + +@pytest.fixture +def measure(): + return Measure() + + +def test_is_operator(measure): + assert isinstance(measure, QuantumOperator) + + +def test_equality(): + measure1 = Measure() + measure2 = Measure() + non_measure = "non measure" + + assert measure1 == measure2 + assert measure1 is not measure2 + assert measure1 != non_measure + + +def test_ascii_symbols(measure): + assert measure.ascii_symbols == ("M",) + + +def test_str(measure): + assert str(measure) == measure.name + + +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + ( + IRType.JAQCD, + None, + NotImplementedError, + "measure instructions are not supported with JAQCD.", + ), + ("invalid-ir-type", None, ValueError, "supplied ir_type invalid-ir-type is not supported."), + ], +) +def test_measure_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, measure +): + with pytest.raises(expected_exception) as exc: + measure.to_ir(ir_type=ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message + + +@pytest.mark.parametrize( + "measure, target, serialization_properties, expected_ir", + [ + ( + Measure(), + [0], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "b[0] = measure q[0];", + ), + ( + Measure(), + [1, 4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "\n".join( + [ + "b[0] = measure $1;", + "b[1] = measure $4;", + ] + ), + ), + ], +) +def test_measure_to_ir_openqasm(measure, target, serialization_properties, expected_ir): + assert ( + measure.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index 96df634cb..f78b4e746 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -1019,3 +1019,81 @@ def __init__(self): "T : │ 0 │", ) _assert_correct_diagram(circ, expected) + + +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2, 3]) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q1 : ───────┤ X ├───●───────────────", + " └───┘ │ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ M ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_with_multiple_measures(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2]).measure(3).measure(1) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q1 : ───────┤ X ├───●─────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ M ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_instructions_after(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .cnot(2, 3) + .measure(0) + .measure(1) + .h(3) + .cnot(3, 4) + .measure([2, 3]) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─────────────", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q1 : ───────┤ X ├───●─────────┤ M ├─────────────", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ H ├───●───┤ M ├─", + " └───┘ └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q4 : ───────────────────────────────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 8c0f8d9a9..43dd06db7 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -262,16 +262,6 @@ def malformatted_results_1(task_metadata_shots, additional_metadata): ).json() -@pytest.fixture -def malformatted_results_2(task_metadata_shots, additional_metadata): - return GateModelTaskResult( - measurementProbabilities={"011000": 0.9999999999999982}, - measuredQubits=[0], - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata, - ).json() - - @pytest.fixture def openqasm_result_obj_shots(task_metadata_shots, additional_metadata_openqasm): return GateModelTaskResult.construct( @@ -484,11 +474,6 @@ def test_shots_no_measurements_no_measurement_probs(malformatted_results_1): GateModelQuantumTaskResult.from_string(malformatted_results_1) -@pytest.mark.xfail(raises=ValueError) -def test_measurements_measured_qubits_mismatch(malformatted_results_2): - GateModelQuantumTaskResult.from_string(malformatted_results_2) - - @pytest.mark.parametrize("ir_result,expected_result", test_ir_results) def test_calculate_ir_results(ir_result, expected_result): ir_string = jaqcd.Program( From 600baf24fdea9307db0aaa2c14be6363b8ee73f7 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 Apr 2024 17:49:47 +0000 Subject: [PATCH 112/347] prepare release v1.76.0 --- CHANGELOG.md | 14 ++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a9aa145..fa01b5469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v1.76.0 (2024-04-01) + +### Features + + * add support for OpenQASM measure on a subset of qubits + +### Bug Fixes and Other Changes + + * restore the dependent test back to pennylane + +### Documentation Changes + + * fix GPI2 gate matrix representation + ## v1.75.0 (2024-03-28) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9c4e62904..8fd998330 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.75.1.dev0" +__version__ = "1.76.0" From d5580dc80b59b9c07ca639e1a20ce37d4ffb2ed4 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 Apr 2024 17:49:47 +0000 Subject: [PATCH 113/347] update development version to v1.76.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8fd998330..2721571fe 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.0" +__version__ = "1.76.1.dev0" From c57a9ba15f0ffb412c9717394690633131538088 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:30:11 -0700 Subject: [PATCH 114/347] fix: prevent repeated measurements on a qubit (#937) * fix: prevent repeated measurements on a qubit * break repeated qubits into two error messages for repeat in measured_targets or repeat in target_qubits --- src/braket/circuits/circuit.py | 12 +++++++++++- test/unit_tests/braket/circuits/test_circuit.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 61fcd38bb..a4b48629d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,6 +13,7 @@ from __future__ import annotations +from collections import Counter from collections.abc import Callable, Iterable from numbers import Number from typing import Any, Optional, TypeVar, Union @@ -762,7 +763,7 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: if target_qubits: # Check if the target_qubits are already measured - if self._measure_targets and all( + if self._measure_targets and any( target in self._measure_targets for target in target_qubits ): intersection = set(target_qubits) & set(self._measure_targets) @@ -770,6 +771,15 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " "more than once." ) + # Check if there are repeated qubits in the same measurement + if len(target_qubits) != len(set(target_qubits)): + intersection = [ + qubit for qubit, count in Counter(target_qubits).items() if count > 1 + ] + raise ValueError( + f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " + "in the same measurement." + ) self._add_measure(target_qubits=target_qubits) else: # Check if any qubits are already measured diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 9b34d6629..7680f9b4f 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -718,6 +718,18 @@ def test_measure_same_qubit_twice(): Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) +def test_measure_same_qubit_twice_with_list(): + message = "cannot measure the same qubit\\(s\\) 0 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure(0).measure([0, 1]) + + +def test_measure_same_qubit_twice_with_one_measure(): + message = "cannot repeat qubit\\(s\\) 0 in the same measurement." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure([0, 0, 0]) + + def test_measure_empty_measure_after_measure_with_targets(): message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." with pytest.raises(ValueError, match=message): From b98bce577a2105f990a4e1aaf726b3802aa45f48 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:36:44 -0700 Subject: [PATCH 115/347] fix: Support single-register measurements in `from_ir` (#934) * fix: add support for measuring a single register in an OpenQASM program * add round trip transformation test from circuit to OQ3 to circuit --- src/braket/circuits/braket_program_context.py | 5 ++- .../braket/circuits/test_circuit.py | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index a17864ed4..852dfba80 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -167,5 +167,6 @@ def add_measure(self, target: tuple[int]) -> None: Args: target (tuple[int]): the target qubits to be measured. """ - instruction = Instruction(Measure(), list(target)) - self._circuit.add_instruction(instruction) + for index, qubit in enumerate(target): + instruction = Instruction(Measure(index=index), qubit) + self._circuit.add_instruction(instruction) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 7680f9b4f..51752e3d4 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -811,6 +811,48 @@ def test_from_ir_with_measure(): assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ +def test_from_ir_with_single_measure(): + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b = measure q;", + ] + ), + inputs={}, + ) + expected_circ = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ + + +def test_from_ir_round_trip_transformation(): + circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ] + ), + inputs={}, + ) + new_ir = circuit.to_ir("OPENQASM") + new_circuit = Circuit.from_ir(new_ir) + + assert new_ir == ir + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == circuit + assert new_circuit == circuit + + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) From 1beeaac7d1c524e7371aaf572a70a00aaeb077fe Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 16:17:27 +0000 Subject: [PATCH 116/347] prepare release v1.76.1 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa01b5469..e125c940c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.76.1 (2024-04-08) + +### Bug Fixes and Other Changes + + * Support single-register measurements in `from_ir` + * prevent repeated measurements on a qubit + ## v1.76.0 (2024-04-01) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2721571fe..7a807475d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.1.dev0" +__version__ = "1.76.1" From e93b8a7c26d1fb9b0b8dc855895ee9b963ec7897 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 16:17:27 +0000 Subject: [PATCH 117/347] update development version to v1.76.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7a807475d..5919ee261 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.1" +__version__ = "1.76.2.dev0" From 1de7dbe81a7388e4ed430c5d9876818373563a39 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:52:17 -0700 Subject: [PATCH 118/347] fix: backwards compatiblity for local detuning (#942) --- .../braket/ahs/test_analog_hamiltonian_simulation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index bdc3e92f2..aeb969869 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -177,7 +177,12 @@ def test_discretize(register, driving_field, shifting_field): "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], }, } - assert discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] == { + local_detuning = ( + discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] + if "shiftingFields" in discretized_json["hamiltonian"].keys() + else discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] + ) + assert local_detuning == { "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], "time_series": { "times": ["0E-9", "0.000003000"], From e17918bfbb9217c2d5d2f6687cfd8beeba2c431e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 21:42:01 +0000 Subject: [PATCH 119/347] prepare release v1.76.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e125c940c..d1aa55863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.76.2 (2024-04-08) + +### Bug Fixes and Other Changes + + * backwards compatiblity for local detuning + ## v1.76.1 (2024-04-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5919ee261..15c11b7c4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.2.dev0" +__version__ = "1.76.2" From 5b21c12121341ba3c42b94ef15251e0bee192498 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 21:42:01 +0000 Subject: [PATCH 120/347] update development version to v1.76.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 15c11b7c4..8692940be 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.2" +__version__ = "1.76.3.dev0" From 64a25363580ed159fe716955fda4aaebdbc06609 Mon Sep 17 00:00:00 2001 From: Li Li <60371004+tachikoma-li@users.noreply.github.com> Date: Wed, 10 Apr 2024 02:09:24 +1000 Subject: [PATCH 121/347] fix: Replace pkg_resources with importlib.metadata (#935) --- doc/conf.py | 5 ++--- setup.py | 2 +- src/braket/devices/local_simulator.py | 12 +++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 2a8193e55..a2548fc65 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,12 +1,11 @@ """Sphinx configuration.""" import datetime - -import pkg_resources +from importlib.metadata import version # Sphinx configuration below. project = "amazon-braket-sdk" -version = pkg_resources.require(project)[0].version +version = version(project) release = version copyright = "{}, Amazon.com".format(datetime.datetime.now().year) diff --git a/setup.py b/setup.py index 6c0a9e984..34d220161 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ "amazon-braket-schemas>=1.21.0", "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", - "setuptools", "backoff", "boltons", "boto3>=1.28.53", @@ -41,6 +40,7 @@ "openpulse", "openqasm3", "sympy", + "backports.entry-points-selectable", ], extras_require={ "test": [ diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index faee13fdf..69fcfdaff 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -13,14 +13,13 @@ from __future__ import annotations +import sys from functools import singledispatchmethod from itertools import repeat from multiprocessing import Pool from os import cpu_count from typing import Any, Optional, Union -import pkg_resources - from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.circuits import Circuit @@ -39,9 +38,12 @@ from braket.tasks.local_quantum_task import LocalQuantumTask from braket.tasks.local_quantum_task_batch import LocalQuantumTaskBatch -_simulator_devices = { - entry.name: entry for entry in pkg_resources.iter_entry_points("braket.simulators") -} +if sys.version_info.minor == 9: + from backports.entry_points_selectable import entry_points +else: + from importlib.metadata import entry_points + +_simulator_devices = {entry.name: entry for entry in entry_points(group="braket.simulators")} class LocalSimulator(Device): From c68722302aec90d0caa2242dec9e0997afb8246d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:43:03 -0400 Subject: [PATCH 122/347] doc: Improve gphase unitary matrix definition in docstring (#944) * Improve gphase unitary matrix definition in docstring * Remove extra whitespace --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/circuits/gates.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index b93b2dfa7..3bfb16731 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -220,7 +220,9 @@ class GPhase(AngledGate): Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} + e^{i \gamma} & 0 \\ + 0 & e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -277,7 +279,9 @@ def gphase( Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} + e^{i \gamma} & 0 \\ + 0 & e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): Phase in radians. From bc8e56a79a500ef1bf046999e90a3f204a0d6d64 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 9 Apr 2024 17:52:29 +0000 Subject: [PATCH 123/347] prepare release v1.76.3 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1aa55863..c97490e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.76.3 (2024-04-09) + +### Bug Fixes and Other Changes + + * Replace pkg_resources with importlib.metadata + +### Documentation Changes + + * Improve gphase unitary matrix definition in docstring + ## v1.76.2 (2024-04-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8692940be..51f057a07 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.3.dev0" +__version__ = "1.76.3" From 3ea6899074c68f788ee2d4271e415ba592dd793c Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 9 Apr 2024 17:52:29 +0000 Subject: [PATCH 124/347] update development version to v1.76.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 51f057a07..e9af02fbc 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.3" +__version__ = "1.76.4.dev0" From e2da4ff2a77c1462636b40442668d5440b2cd126 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:35:11 -0700 Subject: [PATCH 125/347] feat: rename shifting field to local detuning (#943) --- src/braket/ahs/local_detuning.py | 161 +++++++++++++++++++++++++++++++ src/braket/ahs/shifting_field.py | 153 ++--------------------------- 2 files changed, 167 insertions(+), 147 deletions(-) create mode 100644 src/braket/ahs/local_detuning.py diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py new file mode 100644 index 000000000..574b985d3 --- /dev/null +++ b/src/braket/ahs/local_detuning.py @@ -0,0 +1,161 @@ +# 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. + +from __future__ import annotations + +from braket.ahs.discretization_types import DiscretizationProperties +from braket.ahs.field import Field +from braket.ahs.hamiltonian import Hamiltonian +from braket.ahs.pattern import Pattern +from braket.timings.time_series import StitchBoundaryCondition, TimeSeries + + +class LocalDetuning(Hamiltonian): + def __init__(self, magnitude: Field) -> None: + r"""Creates a Hamiltonian term :math:`H_{shift}` representing the local detuning + that changes the energy of the Rydberg level in an AnalogHamiltonianSimulation, + defined by the formula + + .. math:: + H_{shift} (t) := -\Delta(t) \sum_k h_k | r_k \rangle \langle r_k | + + where + + :math:`\Delta(t)` is the magnitude of the frequency shift in rad/s, + + :math:`h_k` is the site coefficient of atom :math:`k`, + a dimensionless real number between 0 and 1, + + :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. + + with the sum :math:`\sum_k` taken over all target atoms. + + Args: + magnitude (Field): containing the global magnitude time series :math:`\Delta(t)`, + where time is measured in seconds (s) and values are measured in rad/s, and the + local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. + """ + super().__init__() + self._magnitude = magnitude + + @property + def terms(self) -> list[Hamiltonian]: + return [self] + + @property + def magnitude(self) -> Field: + r"""Field: containing the global magnitude time series :math:`\Delta(t)`, + where time is measured in seconds (s) and values measured in rad/s) + and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. + """ + return self._magnitude + + @staticmethod + def from_lists(times: list[float], values: list[float], pattern: list[float]) -> LocalDetuning: + """Get the shifting field from a set of time points, values and pattern + + Args: + times (list[float]): The time points of the shifting field + values (list[float]): The values of the shifting field + pattern (list[float]): The pattern of the shifting field + + Raises: + ValueError: If the length of times and values differs. + + Returns: + LocalDetuning: The shifting field obtained + """ + if len(times) != len(values): + raise ValueError("The length of the times and values lists must be equal.") + + magnitude = TimeSeries() + for t, v in zip(times, values): + magnitude.put(t, v) + shift = LocalDetuning(Field(magnitude, Pattern(pattern))) + + return shift + + def stitch( + self, other: LocalDetuning, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN + ) -> LocalDetuning: + """Stitches two shifting fields based on TimeSeries.stitch method. + The time points of the second LocalDetuning are shifted such that the first time point of + the second LocalDetuning coincides with the last time point of the first LocalDetuning. + The boundary point value is handled according to StitchBoundaryCondition argument value. + + Args: + other (LocalDetuning): The second local detuning to be stitched with. + boundary (StitchBoundaryCondition): {"mean", "left", "right"}. Boundary point handler. + + Possible options are + - "mean" - take the average of the boundary value points of the first + and the second time series. + - "left" - use the last value from the left time series as the boundary point. + - "right" - use the first value from the right time series as the boundary point. + + Raises: + ValueError: The LocalDetuning patterns differ. + + Returns: + LocalDetuning: The stitched LocalDetuning object. + + Example (StitchBoundaryCondition.MEAN): + :: + time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) + time_series_2 = TimeSeries.from_lists(times=[0.2, 0.4], values=[4, 5]) + + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 3, 5] + + Example (StitchBoundaryCondition.LEFT): + :: + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 2, 5] + + Example (StitchBoundaryCondition.RIGHT): + :: + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 4, 5] + """ + if not (self.magnitude.pattern.series == other.magnitude.pattern.series): + raise ValueError("The LocalDetuning pattern for both fields must be equal.") + + new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) + return LocalDetuning(Field(new_ts, self.magnitude.pattern)) + + def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: + """Creates a discretized version of the LocalDetuning. + + Args: + properties (DiscretizationProperties): Capabilities of a device that represent the + resolution with which the device can implement the parameters. + + Returns: + LocalDetuning: A new discretized LocalDetuning. + """ + shifting_parameters = properties.rydberg.rydbergLocal + discretized_magnitude = self.magnitude.discretize( + time_resolution=shifting_parameters.timeResolution, + value_resolution=shifting_parameters.commonDetuningResolution, + pattern_resolution=shifting_parameters.localDetuningResolution, + ) + return LocalDetuning(discretized_magnitude) diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index 7bed1fe8a..34e24191e 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -11,151 +11,10 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import annotations +from braket.ahs.local_detuning import LocalDetuning -from braket.ahs.discretization_types import DiscretizationProperties -from braket.ahs.field import Field -from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.pattern import Pattern -from braket.timings.time_series import StitchBoundaryCondition, TimeSeries - - -class ShiftingField(Hamiltonian): - def __init__(self, magnitude: Field) -> None: - r"""Creates a Hamiltonian term :math:`H_{shift}` representing the shifting field - that changes the energy of the Rydberg level in an AnalogHamiltonianSimulation, - defined by the formula - - .. math:: - H_{shift} (t) := -\Delta(t) \sum_k h_k | r_k \rangle \langle r_k | - - where - - :math:`\Delta(t)` is the magnitude of the frequency shift in rad/s, - - :math:`h_k` is the site coefficient of atom :math:`k`, - a dimensionless real number between 0 and 1, - - :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. - - with the sum :math:`\sum_k` taken over all target atoms. - - Args: - magnitude (Field): containing the global magnitude time series :math:`\Delta(t)`, - where time is measured in seconds (s) and values are measured in rad/s, and the - local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. - """ - super().__init__() - self._magnitude = magnitude - - @property - def terms(self) -> list[Hamiltonian]: - return [self] - - @property - def magnitude(self) -> Field: - r"""Field: containing the global magnitude time series :math:`\Delta(t)`, - where time is measured in seconds (s) and values measured in rad/s) - and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. - """ - return self._magnitude - - @staticmethod - def from_lists(times: list[float], values: list[float], pattern: list[float]) -> ShiftingField: - """Get the shifting field from a set of time points, values and pattern - - Args: - times (list[float]): The time points of the shifting field - values (list[float]): The values of the shifting field - pattern (list[float]): The pattern of the shifting field - - Raises: - ValueError: If the length of times and values differs. - - Returns: - ShiftingField: The shifting field obtained - """ - if len(times) != len(values): - raise ValueError("The length of the times and values lists must be equal.") - - magnitude = TimeSeries() - for t, v in zip(times, values): - magnitude.put(t, v) - shift = ShiftingField(Field(magnitude, Pattern(pattern))) - - return shift - - def stitch( - self, other: ShiftingField, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN - ) -> ShiftingField: - """Stitches two shifting fields based on TimeSeries.stitch method. - The time points of the second ShiftingField are shifted such that the first time point of - the second ShiftingField coincides with the last time point of the first ShiftingField. - The boundary point value is handled according to StitchBoundaryCondition argument value. - - Args: - other (ShiftingField): The second shifting field to be stitched with. - boundary (StitchBoundaryCondition): {"mean", "left", "right"}. Boundary point handler. - - Possible options are - - "mean" - take the average of the boundary value points of the first - and the second time series. - - "left" - use the last value from the left time series as the boundary point. - - "right" - use the first value from the right time series as the boundary point. - - Raises: - ValueError: The ShiftingField patterns differ. - - Returns: - ShiftingField: The stitched ShiftingField object. - - Example (StitchBoundaryCondition.MEAN): - :: - time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) - time_series_2 = TimeSeries.from_lists(times=[0.2, 0.4], values=[4, 5]) - - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 3, 5] - - Example (StitchBoundaryCondition.LEFT): - :: - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 2, 5] - - Example (StitchBoundaryCondition.RIGHT): - :: - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 4, 5] - """ - if not (self.magnitude.pattern.series == other.magnitude.pattern.series): - raise ValueError("The ShiftingField pattern for both fields must be equal.") - - new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) - return ShiftingField(Field(new_ts, self.magnitude.pattern)) - - def discretize(self, properties: DiscretizationProperties) -> ShiftingField: - """Creates a discretized version of the ShiftingField. - - Args: - properties (DiscretizationProperties): Capabilities of a device that represent the - resolution with which the device can implement the parameters. - - Returns: - ShiftingField: A new discretized ShiftingField. - """ - shifting_parameters = properties.rydberg.rydbergLocal - discretized_magnitude = self.magnitude.discretize( - time_resolution=shifting_parameters.timeResolution, - value_resolution=shifting_parameters.commonDetuningResolution, - pattern_resolution=shifting_parameters.localDetuningResolution, - ) - return ShiftingField(discretized_magnitude) +# The class `ShiftingField` is deprecated. Please use `LocalDetuning` instead. +# This file and class will be removed in a future version. +# We are retaining this now to avoid breaking backwards compatibility for users already +# utilizing this nomenclature. +ShiftingField = LocalDetuning From bf075075a2aed6aeadaf65c5743707c41cda29b4 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 01:12:49 +0000 Subject: [PATCH 126/347] prepare release v1.77.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c97490e5a..3f8fb8909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.0 (2024-04-10) + +### Features + + * rename shifting field to local detuning + ## v1.76.3 (2024-04-09) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e9af02fbc..158bc4c48 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.4.dev0" +__version__ = "1.77.0" From f5565675dd3e6cdfc8cbac1ce1420107b7108850 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 01:12:49 +0000 Subject: [PATCH 127/347] update development version to v1.77.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 158bc4c48..f2ce90f8f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.0" +__version__ = "1.77.1.dev0" From 78a49696f618b63a997929c3407be29f67535de7 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:50:34 -0700 Subject: [PATCH 128/347] fix: add measure qubit targets in braket_program_context (#947) --- src/braket/circuits/braket_program_context.py | 4 ++++ test/unit_tests/braket/circuits/test_circuit.py | 10 +++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 852dfba80..4371637d3 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -170,3 +170,7 @@ def add_measure(self, target: tuple[int]) -> None: for index, qubit in enumerate(target): instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) + if self._circuit._measure_targets: + self._circuit._measure_targets.append(qubit) + else: + self._circuit._measure_targets = [qubit] diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 51752e3d4..d7f6568d4 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -839,18 +839,14 @@ def test_from_ir_round_trip_transformation(): "qubit[2] q;", "h q[0];", "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "b = measure q;", ] ), inputs={}, ) - new_ir = circuit.to_ir("OPENQASM") - new_circuit = Circuit.from_ir(new_ir) - assert new_ir == ir - assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == circuit - assert new_circuit == circuit + assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") def test_add_with_instruction_with_default(cnot_instr): From fc045fa096313855397ffdeb37566a6e1c4098f6 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 21:13:43 +0000 Subject: [PATCH 129/347] prepare release v1.77.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8fb8909..9384714f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.1 (2024-04-10) + +### Bug Fixes and Other Changes + + * add measure qubit targets in braket_program_context + ## v1.77.0 (2024-04-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f2ce90f8f..64b837643 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.1.dev0" +__version__ = "1.77.1" From a60d715f0643bcc4ae966c4ba68c012c77030cbc Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 21:13:43 +0000 Subject: [PATCH 130/347] update development version to v1.77.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 64b837643..7d0928a71 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.1" +__version__ = "1.77.2.dev0" From e1564a42ac8b898f021bc7d2fbae370338f5ea50 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:34:56 -0700 Subject: [PATCH 131/347] fix: remove shifting field from testing (#948) --- setup.py | 2 +- src/braket/ahs/__init__.py | 1 + .../ahs/analog_hamiltonian_simulation.py | 10 ++-- .../ahs/test_analog_hamiltonian_simulation.py | 20 ++++---- ...ifting_field.py => test_local_detuning.py} | 48 +++++++++---------- ...alog_hamiltonian_simulation_task_result.py | 2 +- 6 files changed, 40 insertions(+), 43 deletions(-) rename test/unit_tests/braket/ahs/{test_shifting_field.py => test_local_detuning.py} (73%) diff --git a/setup.py b/setup.py index 34d220161..821748ef3 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.21.0", + "amazon-braket-schemas>=1.21.3", "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", "backoff", diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py index 8a9fd2666..5bf3cc61e 100644 --- a/src/braket/ahs/__init__.py +++ b/src/braket/ahs/__init__.py @@ -17,5 +17,6 @@ from braket.ahs.driving_field import DrivingField # noqa: F401 from braket.ahs.field import Field # noqa: F401 from braket.ahs.hamiltonian import Hamiltonian # noqa: F401 +from braket.ahs.local_detuning import LocalDetuning # noqa: F401 from braket.ahs.pattern import Pattern # noqa: F401 from braket.ahs.shifting_field import ShiftingField # noqa: F401 diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index 8d8cce10f..af02471e2 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -21,12 +21,12 @@ from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties from braket.ahs.driving_field import DrivingField from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.shifting_field import ShiftingField +from braket.ahs.local_detuning import LocalDetuning from braket.device_schema import DeviceActionType class AnalogHamiltonianSimulation: - SHIFTING_FIELDS_PROPERTY = "shifting_fields" + LOCAL_DETUNING_PROPERTY = "local_detuning" DRIVING_FIELDS_PROPERTY = "driving_fields" def __init__(self, register: AtomArrangement, hamiltonian: Hamiltonian) -> None: @@ -74,7 +74,7 @@ def _hamiltonian_to_ir(self) -> ir.Hamiltonian: terms[term_type].append(term_ir) return ir.Hamiltonian( drivingFields=terms[AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY], - shiftingFields=terms[AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY], + localDetuning=terms[AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY], ) def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: # noqa @@ -116,8 +116,8 @@ def _get_term_ir( @_get_term_ir.register -def _(term: ShiftingField) -> tuple[str, ir.ShiftingField]: - return AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY, ir.ShiftingField( +def _(term: LocalDetuning) -> tuple[str, ir.LocalDetuning]: + return AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY, ir.LocalDetuning( magnitude=ir.PhysicalField( time_series=ir.TimeSeries( times=term.magnitude.time_series.times(), diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index aeb969869..65cc2a074 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -23,7 +23,7 @@ AtomArrangement, DiscretizationError, DrivingField, - ShiftingField, + LocalDetuning, SiteType, ) from braket.ahs.atom_arrangement import AtomArrangementItem @@ -61,8 +61,8 @@ def driving_field(): @pytest.fixture -def shifting_field(): - return ShiftingField( +def local_detuning(): + return LocalDetuning( Field( TimeSeries().put(0.0, -1.25664e8).put(3.0e-6, 1.25664e8), Pattern([0.5, 1.0, 0.5, 0.5, 0.5, 0.5]), @@ -78,8 +78,8 @@ def test_create(): assert mock1 == ahs.hamiltonian -def test_to_ir(register, driving_field, shifting_field): - hamiltonian = driving_field + shifting_field +def test_to_ir(register, driving_field, local_detuning): + hamiltonian = driving_field + local_detuning ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) problem = ahs.to_ir() assert Program.parse_raw(problem.json()) == problem @@ -123,8 +123,8 @@ def test_invalid_action_name(): AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) -def test_discretize(register, driving_field, shifting_field): - hamiltonian = driving_field + shifting_field +def test_discretize(register, driving_field, local_detuning): + hamiltonian = driving_field + local_detuning ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) action = Mock() @@ -177,11 +177,7 @@ def test_discretize(register, driving_field, shifting_field): "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], }, } - local_detuning = ( - discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] - if "shiftingFields" in discretized_json["hamiltonian"].keys() - else discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] - ) + local_detuning = discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] assert local_detuning == { "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], "time_series": { diff --git a/test/unit_tests/braket/ahs/test_shifting_field.py b/test/unit_tests/braket/ahs/test_local_detuning.py similarity index 73% rename from test/unit_tests/braket/ahs/test_shifting_field.py rename to test/unit_tests/braket/ahs/test_local_detuning.py index 0249b8758..4c4f94675 100644 --- a/test/unit_tests/braket/ahs/test_shifting_field.py +++ b/test/unit_tests/braket/ahs/test_local_detuning.py @@ -16,49 +16,49 @@ import pytest from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.shifting_field import ShiftingField +from braket.ahs.local_detuning import LocalDetuning from braket.timings.time_series import StitchBoundaryCondition @pytest.fixture -def default_shifting_field(): - return ShiftingField(Mock()) +def default_local_detuning(): + return LocalDetuning(Mock()) def test_create(): mock0 = Mock() - field = ShiftingField(magnitude=mock0) + field = LocalDetuning(magnitude=mock0) assert mock0 == field.magnitude -def test_add_hamiltonian(default_shifting_field): - expected = [default_shifting_field, Mock(), Mock(), Mock()] +def test_add_hamiltonian(default_local_detuning): + expected = [default_local_detuning, Mock(), Mock(), Mock()] result = expected[0] + Hamiltonian([expected[1], expected[2], expected[3]]) assert result.terms == expected -def test_add_to_hamiltonian(default_shifting_field): - expected = [Mock(), Mock(), Mock(), default_shifting_field] +def test_add_to_hamiltonian(default_local_detuning): + expected = [Mock(), Mock(), Mock(), default_local_detuning] result = Hamiltonian([expected[0], expected[1], expected[2]]) + expected[3] assert result.terms == expected def test_add_to_other(): - field0 = ShiftingField(Mock()) - field1 = ShiftingField(Mock()) + field0 = LocalDetuning(Mock()) + field1 = LocalDetuning(Mock()) result = field0 + field1 assert type(result) is Hamiltonian assert result.terms == [field0, field1] -def test_add_to_self(default_shifting_field): - result = default_shifting_field + default_shifting_field +def test_add_to_self(default_local_detuning): + result = default_local_detuning + default_local_detuning assert type(result) is Hamiltonian - assert result.terms == [default_shifting_field, default_shifting_field] + assert result.terms == [default_local_detuning, default_local_detuning] -def test_iadd_to_other(default_shifting_field): - expected = [Mock(), Mock(), Mock(), default_shifting_field] +def test_iadd_to_other(default_local_detuning): + expected = [Mock(), Mock(), Mock(), default_local_detuning] other = Hamiltonian([expected[0], expected[1], expected[2]]) other += expected[3] assert other.terms == expected @@ -69,7 +69,7 @@ def test_from_lists(): glob_amplitude = [0.5, 0.8, 0.9, 1.0] pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - sh_field = ShiftingField.from_lists(times, glob_amplitude, pattern) + sh_field = LocalDetuning.from_lists(times, glob_amplitude, pattern) assert sh_field.magnitude.time_series.values() == glob_amplitude assert sh_field.magnitude.pattern.series == pattern @@ -82,7 +82,7 @@ def test_from_lists_not_eq_length(): glob_amplitude = [0.5, 0.8, 0.9, 1.0] pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - ShiftingField.from_lists(times, glob_amplitude, pattern) + LocalDetuning.from_lists(times, glob_amplitude, pattern) def test_stitch(): @@ -94,8 +94,8 @@ def test_stitch(): glob_amplitude_2 = [0.5, 0.8, 0.9, 1.0] pattern_2 = pattern_1 - sh_field_1 = ShiftingField.from_lists(times_1, glob_amplitude_1, pattern_1) - sh_field_2 = ShiftingField.from_lists(times_2, glob_amplitude_2, pattern_2) + sh_field_1 = LocalDetuning.from_lists(times_1, glob_amplitude_1, pattern_1) + sh_field_2 = LocalDetuning.from_lists(times_2, glob_amplitude_2, pattern_2) new_sh_field = sh_field_1.stitch(sh_field_2, boundary=StitchBoundaryCondition.LEFT) @@ -116,8 +116,8 @@ def test_stitch_not_eq_pattern(): glob_amplitude_2 = [0.5, 0.8, 0.9, 1.0] pattern_2 = [-0.3, 0.7, 0.6, -0.5, 0, 1.6] - sh_field_1 = ShiftingField.from_lists(times_1, glob_amplitude_1, pattern_1) - sh_field_2 = ShiftingField.from_lists(times_2, glob_amplitude_2, pattern_2) + sh_field_1 = LocalDetuning.from_lists(times_1, glob_amplitude_1, pattern_1) + sh_field_2 = LocalDetuning.from_lists(times_2, glob_amplitude_2, pattern_2) sh_field_1.stitch(sh_field_2) @@ -125,7 +125,7 @@ def test_stitch_not_eq_pattern(): def test_discretize(): magnitude_mock = Mock() mock_properties = Mock() - field = ShiftingField(magnitude=magnitude_mock) + field = LocalDetuning(magnitude=magnitude_mock) discretized_field = field.discretize(mock_properties) magnitude_mock.discretize.assert_called_with( time_resolution=mock_properties.rydberg.rydbergLocal.timeResolution, @@ -137,5 +137,5 @@ def test_discretize(): @pytest.mark.xfail(raises=ValueError) -def test_iadd_to_itself(default_shifting_field): - default_shifting_field += Hamiltonian(Mock()) +def test_iadd_to_itself(default_local_detuning): + default_local_detuning += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py b/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py index 23cca78be..be2d6fdbe 100644 --- a/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py +++ b/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py @@ -75,7 +75,7 @@ def additional_metadata(): }, } ], - "shiftingFields": [ + "localDetuning": [ { "magnitude": { "time_series": { From da70fffe982ce6fdc2efb1682290f1cabc42aed7 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 22:54:53 +0000 Subject: [PATCH 132/347] prepare release v1.77.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9384714f1..ca418c447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.2 (2024-04-10) + +### Bug Fixes and Other Changes + + * remove shifting field from testing + ## v1.77.1 (2024-04-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7d0928a71..279cf6894 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.2.dev0" +__version__ = "1.77.2" From 04d8a957c88fa6d2a6f9ecdf48d940a0d33fef1a Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 22:54:53 +0000 Subject: [PATCH 133/347] update development version to v1.77.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 279cf6894..5395acb5e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.2" +__version__ = "1.77.3.dev0" From 18aa64e880818f5d7c15575728e427b9fa7cb677 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:37:13 -0700 Subject: [PATCH 134/347] fix: measure target qubits are required (#940) * fix: measure target qubits are required * removed commented out code * fix test name that changed during merge conflict resolution * convert non iterable targets to QubitSet, reformat conditions, add test for target_mapping to increase coverage * move check for already measured target qubits to check_if_qubit_measured function * simplify previously measured qubit error message * pull in the latest default-simulator version * Apply suggestions from code review Co-authored-by: Cody Wang * Update src/braket/circuits/circuit.py * Apply suggestions from code review --------- Co-authored-by: Cody Wang --- setup.py | 2 +- src/braket/circuits/circuit.py | 82 ++++++------------- src/braket/circuits/moments.py | 6 +- .../circuits/test_ascii_circuit_diagram.py | 21 +++++ .../braket/circuits/test_circuit.py | 80 ++++++++++++++---- .../circuits/test_unicode_circuit_diagram.py | 22 +++++ 6 files changed, 137 insertions(+), 76 deletions(-) diff --git a/setup.py b/setup.py index 821748ef3..12bec6f07 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.21.2", + "amazon-braket-default-simulator>=1.21.4", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index a4b48629d..9bac39a6c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -440,22 +440,17 @@ def _check_if_qubit_measured( Raises: ValueError: If adding a gate or noise operation after a measure instruction. """ - if ( - # check if there is a measure instruction on the target qubit - target - and target in self._measure_targets - # check if there is a measure instruction on any qubits in the target_mapping - or (target_mapping and any(targ in self._measure_targets for targ in target_mapping)) - # If no target or target_mapping is supplied, check if there is a measure - # instruction on the current instructions target qubit - or ( - instruction.target - and any(targ in self._measure_targets for targ in instruction.target) - ) - ): - raise ValueError( - "cannot add a gate or noise operation on a qubit after a measure instruction." + if self._measure_targets: + measure_on_target_mapping = target_mapping and any( + targ in self._measure_targets for targ in target_mapping.values() ) + if ( + # check if there is a measure instruction on the targeted qubit(s) + measure_on_target_mapping + or any(tar in self._measure_targets for tar in QubitSet(target)) + or any(tar in self._measure_targets for tar in QubitSet(instruction.target)) + ): + raise ValueError("cannot apply instruction to measured qubits.") def add_instruction( self, @@ -510,8 +505,7 @@ def add_instruction( raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") # Check if there is a measure instruction on the circuit - if not isinstance(instruction.operator, Measure) and self._measure_targets: - self._check_if_qubit_measured(instruction, target, target_mapping) + self._check_if_qubit_measured(instruction, target, target_mapping) if not target_mapping and not target: # Nothing has been supplied, add instruction @@ -724,13 +718,12 @@ def _add_measure(self, target_qubits: QubitSetInput) -> None: else: self._measure_targets = [target] - def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: + def measure(self, target_qubits: QubitSetInput) -> Circuit: """ Add a `measure` operator to `self` ensuring only the target qubits are measured. Args: - target_qubits (QubitSetInput | None): target qubits to measure. - Default=None + target_qubits (QubitSetInput): target qubits to measure. Returns: Circuit: self @@ -750,47 +743,21 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] """ - # check whether measuring an empty circuit - if not self.qubits: - raise IndexError("cannot measure an empty circuit.") - - if isinstance(target_qubits, int): - target_qubits = [target_qubits] + if not isinstance(target_qubits, Iterable): + target_qubits = QubitSet(target_qubits) # Check if result types are added on the circuit if self.result_types: raise ValueError("a circuit cannot contain both measure instructions and result types.") - if target_qubits: - # Check if the target_qubits are already measured - if self._measure_targets and any( - target in self._measure_targets for target in target_qubits - ): - intersection = set(target_qubits) & set(self._measure_targets) - raise ValueError( - f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " - "more than once." - ) - # Check if there are repeated qubits in the same measurement - if len(target_qubits) != len(set(target_qubits)): - intersection = [ - qubit for qubit, count in Counter(target_qubits).items() if count > 1 - ] - raise ValueError( - f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " - "in the same measurement." - ) - self._add_measure(target_qubits=target_qubits) - else: - # Check if any qubits are already measured - if self._measure_targets: - intersection = set(self.qubits) & set(self._measure_targets) - raise ValueError( - f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " - "more than once." - ) - # Measure all the qubits - self._add_measure(target_qubits=self.qubits) + # Check if there are repeated qubits in the same measurement + if len(target_qubits) != len(set(target_qubits)): + intersection = [qubit for qubit, count in Counter(target_qubits).items() if count > 1] + raise ValueError( + f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " + "in the same measurement." + ) + self._add_measure(target_qubits=target_qubits) return self @@ -916,6 +883,9 @@ def apply_gate_noise( if not all(qubit in self.qubits for qubit in target_qubits): raise IndexError("target_qubits must be within the range of the current circuit.") + # Check if there is a measure instruction on the circuit + self._check_if_qubit_measured(instruction=noise, target=target_qubits) + # make noise a list noise = wrap_with_list(noise) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 031c2f5e5..6622b3467 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -257,6 +257,7 @@ def sort_moments(self) -> None: key_readout_noise = [] moment_copy = OrderedDict() sorted_moment = OrderedDict() + last_measure = self._depth for key, instruction in self._moments.items(): moment_copy[key] = instruction @@ -264,6 +265,9 @@ def sort_moments(self) -> None: key_readout_noise.append(key) elif key.moment_type == MomentType.INITIALIZATION_NOISE: key_initialization_noise.append(key) + elif key.moment_type == MomentType.MEASURE: + last_measure = key.time + key_noise.append(key) else: key_noise.append(key) @@ -272,7 +276,7 @@ def sort_moments(self) -> None: for key in key_noise: sorted_moment[key] = moment_copy[key] # find the max time in the circuit and make it the time for readout noise - max_time = max(self._depth - 1, 0) + max_time = max(last_measure - 1, 0) for key in key_readout_noise: sorted_moment[ diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 7724d9077..aec875568 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -20,6 +20,7 @@ FreeParameter, Gate, Instruction, + Noise, Observable, Operator, ) @@ -935,3 +936,23 @@ def test_measure_multiple_instructions_after(): "T : |0|1|2|3|4|5|6|", ) _assert_correct_diagram(circ, expected) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + "T : |0| 1 |2|", + " ", + "q0 : -H-C---------M-", + " | ", + "q1 : ---X-BF(0.1)-M-", + "", + "T : |0| 1 |2|", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index d7f6568d4..b29f3b995 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -24,6 +24,7 @@ Gate, Instruction, Moments, + Noise, Observable, QubitSet, ResultType, @@ -670,22 +671,26 @@ def test_measure_qubits_out_of_range(): def test_measure_empty_circuit(): - with pytest.raises(IndexError): - Circuit().measure() - - -def test_measure_no_target(): - circ = Circuit().h(0).cnot(0, 1).measure() + circ = Circuit().measure([0, 1, 2]) expected = ( Circuit() - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) .add_instruction(Instruction(Measure(), 0)) .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 2)) ) assert circ == expected +def test_measure_target_input(): + message = "Supplied qubit index, 1.1, must be an integer." + with pytest.raises(TypeError, match=message): + Circuit().h(0).cnot(0, 1).measure(1.1) + + message = "Supplied qubit index, a, must be an integer." + with pytest.raises(TypeError, match=message): + Circuit().h(0).cnot(0, 1).measure(FreeParameter("a")) + + def test_measure_with_result_types(): message = "a circuit cannot contain both measure instructions and result types." with pytest.raises(ValueError, match=message): @@ -713,13 +718,15 @@ def test_measure_with_multiple_measures(): def test_measure_same_qubit_twice(): - message = "cannot measure the same qubit\\(s\\) 0 more than once." + # message = "cannot measure the same qubit\\(s\\) Qubit\\(0\\) more than once." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) def test_measure_same_qubit_twice_with_list(): - message = "cannot measure the same qubit\\(s\\) 0 more than once." + # message = "cannot measure the same qubit\\(s\\) Qubit\\(0\\) more than once." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).measure(0).measure([0, 1]) @@ -730,20 +737,56 @@ def test_measure_same_qubit_twice_with_one_measure(): Circuit().h(0).cnot(0, 1).measure([0, 0, 0]) -def test_measure_empty_measure_after_measure_with_targets(): - message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." +def test_measure_gate_after(): + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): - Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(1).measure() + Circuit().h(0).measure(0).h([0, 1]) + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." + with pytest.raises(ValueError, match=message): + instr = Instruction(Gate.CNot(), [0, 1]) + Circuit().measure([0, 1]).add_instruction(instr, target_mapping={0: 0, 1: 1}) -def test_measure_gate_after(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): - Circuit().h(0).measure(0).h([0, 1]) + instr = Instruction(Gate.CNot(), [0, 1]) + Circuit().h(0).measure(0).add_instruction(instr, target=[0, 1]) + + +def test_measure_noise_after(): + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." + with pytest.raises(ValueError, match=message): + Circuit().h(1).h(1).h(2).h(5).h(4).h(3).cnot(1, 2).measure([0, 1, 2, 3, 4]).kraus( + targets=[0], matrices=[np.array([[1, 0], [0, 1]])] + ) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + ) + assert circ == expected def test_measure_gate_after_with_target_mapping(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction( @@ -752,7 +795,8 @@ def test_measure_gate_after_with_target_mapping(): def test_measure_gate_after_with_target(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction(instr, target=[10, 11]) diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index f78b4e746..268c53a96 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -19,6 +19,7 @@ FreeParameter, Gate, Instruction, + Noise, Observable, Operator, UnicodeCircuitDiagram, @@ -1097,3 +1098,24 @@ def test_measure_multiple_instructions_after(): "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", ) _assert_correct_diagram(circ, expected) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌─────────┐ ┌───┐ ", + "q1 : ───────┤ X ├─┤ BF(0.1) ├─┤ M ├─", + " └───┘ └─────────┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) From 7831de00732f8cc6cf5455396e72524c50694ad4 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Apr 2024 16:16:32 +0000 Subject: [PATCH 135/347] prepare release v1.77.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca418c447..6449e1bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.3 (2024-04-11) + +### Bug Fixes and Other Changes + + * measure target qubits are required + ## v1.77.2 (2024-04-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5395acb5e..5fd378c39 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3.dev0" +__version__ = "1.77.3" From 53aba5eb8f8fed19280c96e980844663f7fc5f92 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Apr 2024 16:16:32 +0000 Subject: [PATCH 136/347] update development version to v1.77.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5fd378c39..292d3ff75 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3" +__version__ = "1.77.4.dev0" From b1cd32da7d0c24bec2a0f7a5cf04b1b3f08bf6e3 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:32:38 -0400 Subject: [PATCH 137/347] doc: correct gphase matrix representation (#946) --- src/braket/circuits/gates.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 3bfb16731..84ff3de05 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -220,9 +220,8 @@ class GPhase(AngledGate): Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} - e^{i \gamma} & 0 \\ - 0 & e^{i \gamma} \end{bmatrix}. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I_1 = \begin{bmatrix} + e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -279,9 +278,8 @@ def gphase( Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} - e^{i \gamma} & 0 \\ - 0 & e^{i \gamma} \end{bmatrix}. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I_1 = \begin{bmatrix} + e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): Phase in radians. From a18d445dce4039c9756c26f93e731f4922ab8225 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 15 Apr 2024 16:17:24 +0000 Subject: [PATCH 138/347] prepare release v1.77.3.post0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6449e1bea..c3956a4da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.3.post0 (2024-04-15) + +### Documentation Changes + + * correct gphase matrix representation + ## v1.77.3 (2024-04-11) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 292d3ff75..8bdbbfafd 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4.dev0" +__version__ = "1.77.3.post0" From 6e9a2dcb10d0b29c5458985873a2bea127582351 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 15 Apr 2024 16:17:24 +0000 Subject: [PATCH 139/347] update development version to v1.77.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8bdbbfafd..292d3ff75 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3.post0" +__version__ = "1.77.4.dev0" From 5ed6590299584bc2aeefd860a21225ff4c04a168 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:35:54 -0700 Subject: [PATCH 140/347] fix: discretize method now takes None as an arg (#950) --- src/braket/ahs/driving_field.py | 12 +++++++--- src/braket/ahs/field.py | 16 ++++--------- src/braket/ahs/local_detuning.py | 11 +++++---- src/braket/ahs/pattern.py | 12 +++++++--- src/braket/timings/time_series.py | 24 +++++++++++++------ test/unit_tests/braket/ahs/test_field.py | 18 +++++--------- test/unit_tests/braket/ahs/test_pattern.py | 22 ++++++++++++++++- .../braket/timings/test_time_series.py | 21 ++++++++++------ 8 files changed, 87 insertions(+), 49 deletions(-) diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index f6c6430f2..cbf01838d 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -122,17 +122,23 @@ def discretize(self, properties: DiscretizationProperties) -> DrivingField: """ driving_parameters = properties.rydberg.rydbergGlobal time_resolution = driving_parameters.timeResolution + + amplitude_value_resolution = driving_parameters.rabiFrequencyResolution discretized_amplitude = self.amplitude.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.rabiFrequencyResolution, + value_resolution=amplitude_value_resolution, ) + + phase_value_resolution = driving_parameters.phaseResolution discretized_phase = self.phase.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.phaseResolution, + value_resolution=phase_value_resolution, ) + + detuning_value_resolution = driving_parameters.detuningResolution discretized_detuning = self.detuning.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.detuningResolution, + value_resolution=detuning_value_resolution, ) return DrivingField( amplitude=discretized_amplitude, phase=discretized_phase, detuning=discretized_detuning diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index 9a473fd99..1522b9d65 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -16,7 +16,6 @@ from decimal import Decimal from typing import Optional -from braket.ahs.discretization_types import DiscretizationError from braket.ahs.pattern import Pattern from braket.timings.time_series import TimeSeries @@ -44,8 +43,8 @@ def pattern(self) -> Optional[Pattern]: def discretize( self, - time_resolution: Decimal, - value_resolution: Decimal, + time_resolution: Optional[Decimal] = None, + value_resolution: Optional[Decimal] = None, pattern_resolution: Optional[Decimal] = None, ) -> Field: """Creates a discretized version of the field, @@ -53,24 +52,17 @@ def discretize( closest multiple of their corresponding resolutions. Args: - time_resolution (Decimal): Time resolution - value_resolution (Decimal): Value resolution + time_resolution (Optional[Decimal]): Time resolution + value_resolution (Optional[Decimal]): Value resolution pattern_resolution (Optional[Decimal]): Pattern resolution Returns: Field: A new discretized field. - - Raises: - ValueError: if pattern_resolution is None, but there is a Pattern """ discretized_time_series = self.time_series.discretize(time_resolution, value_resolution) if self.pattern is None: discretized_pattern = None else: - if pattern_resolution is None: - raise DiscretizationError( - f"{self.pattern} is defined but has no pattern_resolution defined" - ) discretized_pattern = self.pattern.discretize(pattern_resolution) discretized_field = Field(time_series=discretized_time_series, pattern=discretized_pattern) return discretized_field diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 574b985d3..1906ce837 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -152,10 +152,13 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: Returns: LocalDetuning: A new discretized LocalDetuning. """ - shifting_parameters = properties.rydberg.rydbergLocal + local_detuning_parameters = properties.rydberg.rydbergLocal + time_resolution = local_detuning_parameters.timeResolution + value_resolution = local_detuning_parameters.commonDetuningResolution + pattern_resolution = local_detuning_parameters.localDetuningResolution discretized_magnitude = self.magnitude.discretize( - time_resolution=shifting_parameters.timeResolution, - value_resolution=shifting_parameters.commonDetuningResolution, - pattern_resolution=shifting_parameters.localDetuningResolution, + time_resolution=time_resolution, + value_resolution=value_resolution, + pattern_resolution=pattern_resolution, ) return LocalDetuning(discretized_magnitude) diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py index 462f0e369..92637fe0f 100644 --- a/src/braket/ahs/pattern.py +++ b/src/braket/ahs/pattern.py @@ -15,6 +15,7 @@ from decimal import Decimal from numbers import Number +from typing import Optional class Pattern: @@ -34,16 +35,21 @@ def series(self) -> list[Number]: """ return self._series - def discretize(self, resolution: Decimal) -> Pattern: + def discretize(self, resolution: Optional[Decimal]) -> Pattern: """Creates a discretized version of the pattern, where each value is rounded to the closest multiple of the resolution. Args: - resolution (Decimal): Resolution of the discretization + resolution (Optional[Decimal]): Resolution of the discretization Returns: Pattern: The new discretized pattern """ - discretized_series = [round(Decimal(num) / resolution) * resolution for num in self.series] + if resolution is None: + discretized_series = [Decimal(num) for num in self.series] + else: + discretized_series = [ + round(Decimal(num) / resolution) * resolution for num in self.series + ] return Pattern(series=discretized_series) diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index a558bec71..afdabd726 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -19,6 +19,7 @@ from decimal import Decimal from enum import Enum from numbers import Number +from typing import Optional @dataclass @@ -267,24 +268,33 @@ def stitch( return new_time_series - def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> TimeSeries: + def discretize( + self, time_resolution: Optional[Decimal], value_resolution: Optional[Decimal] + ) -> TimeSeries: """Creates a discretized version of the time series, rounding all times and values to the closest multiple of the corresponding resolution. Args: - time_resolution (Decimal): Time resolution - value_resolution (Decimal): Value resolution + time_resolution (Optional[Decimal]): Time resolution + value_resolution (Optional[Decimal]): Value resolution Returns: TimeSeries: A new discretized time series. """ discretized_ts = TimeSeries() for item in self: - discretized_ts.put( - time=round(Decimal(item.time) / time_resolution) * time_resolution, - value=round(Decimal(item.value) / value_resolution) * value_resolution, - ) + if time_resolution is None: + discretized_time = Decimal(item.time) + else: + discretized_time = round(Decimal(item.time) / time_resolution) * time_resolution + + if value_resolution is None: + discretized_value = Decimal(item.value) + else: + discretized_value = round(Decimal(item.value) / value_resolution) * value_resolution + + discretized_ts.put(time=discretized_time, value=discretized_value) return discretized_ts @staticmethod diff --git a/test/unit_tests/braket/ahs/test_field.py b/test/unit_tests/braket/ahs/test_field.py index 4212ba336..2ff6714ce 100644 --- a/test/unit_tests/braket/ahs/test_field.py +++ b/test/unit_tests/braket/ahs/test_field.py @@ -16,7 +16,6 @@ import pytest -from braket.ahs.discretization_types import DiscretizationError from braket.ahs.field import Field from braket.ahs.pattern import Pattern from braket.timings.time_series import TimeSeries @@ -80,6 +79,12 @@ def test_discretize( [ (Decimal("0.1"), Decimal("10"), Decimal("0.5")), (Decimal("10"), Decimal("20"), None), + (Decimal("0.1"), None, Decimal("0.5")), + (None, Decimal("10"), Decimal("0.5")), + (None, None, Decimal("0.5")), + (None, Decimal("10"), None), + (Decimal("0.1"), None, None), + (None, None, None), (Decimal("100"), Decimal("0.1"), Decimal("1")), ], ) @@ -93,14 +98,3 @@ def test_uniform_field( ) or expected.pattern.series == actual.pattern.series assert expected.time_series.times() == actual.time_series.times() assert expected.time_series.values() == actual.time_series.values() - - -@pytest.mark.parametrize( - "time_res, value_res, pattern_res", - [ - (Decimal("10"), Decimal("20"), None), - ], -) -@pytest.mark.xfail(raises=DiscretizationError) -def test_invalid_pattern_res(default_field, time_res, value_res, pattern_res): - default_field.discretize(time_res, value_res, pattern_res) diff --git a/test/unit_tests/braket/ahs/test_pattern.py b/test/unit_tests/braket/ahs/test_pattern.py index d84f3a925..920f2cc29 100644 --- a/test/unit_tests/braket/ahs/test_pattern.py +++ b/test/unit_tests/braket/ahs/test_pattern.py @@ -20,7 +20,15 @@ @pytest.fixture def default_values(): - return [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + return [ + Decimal(0), + Decimal("0.1"), + Decimal(1), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0.001"), + Decimal("1e-10"), + ] @pytest.fixture @@ -38,6 +46,18 @@ def test_create(): "res, expected_series", [ # default pattern: [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + ( + None, + [ + Decimal("0"), + Decimal("0.1"), + Decimal("1"), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0.001"), + Decimal("1e-10"), + ], + ), ( Decimal("0.001"), [ diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py index 0f99ca650..c5a26334f 100755 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -20,7 +20,12 @@ @pytest.fixture def default_values(): - return [(2700, 25.1327), (300, 25.1327), (600, 15.1327), (Decimal(0.3), Decimal(0.4))] + return [ + (2700, Decimal("25.1327")), + (300, Decimal("25.1327")), + (600, Decimal("15.1327")), + (Decimal("0.3"), Decimal("0.4")), + ] @pytest.fixture @@ -265,11 +270,12 @@ def test_stitch_wrong_bndry_value(): @pytest.mark.parametrize( "time_res, expected_times", [ - # default_time_series: [(Decimal(0.3), Decimal(0.4), (300, 25.1327), (600, 15.1327), (2700, 25.1327))] # noqa - (Decimal(0.5), [Decimal("0.5"), Decimal("300"), Decimal("600"), Decimal("2700")]), - (Decimal(1), [Decimal("0"), Decimal("300"), Decimal("600"), Decimal("2700")]), - (Decimal(200), [Decimal("0"), Decimal("400"), Decimal("600"), Decimal("2800")]), - (Decimal(1000), [Decimal("0"), Decimal("1000"), Decimal("3000")]), + # default_time_series: [(Decimal(0.3), Decimal(0.4)), (300, 25.1327), (600, 15.1327), (2700, 25.1327)] # noqa + (None, [Decimal("0.3"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("0.5"), [Decimal("0.5"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("1"), [Decimal("0"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("200"), [Decimal("0"), Decimal("400"), Decimal("600"), Decimal("2800")]), + (Decimal("1000"), [Decimal("0"), Decimal("1000"), Decimal("3000")]), ], ) def test_discretize_times(default_time_series, time_res, expected_times): @@ -280,7 +286,8 @@ def test_discretize_times(default_time_series, time_res, expected_times): @pytest.mark.parametrize( "value_res, expected_values", [ - # default_time_series: [(Decimal(0.3), Decimal(0.4), (300, 25.1327), (600, 15.1327), (2700, 25.1327))] # noqa + # default_time_series: [(Decimal(0.3), Decimal(0.4)), (300, 25.1327), (600, 15.1327), (2700, 25.1327)] # noqa + (None, [Decimal("0.4"), Decimal("25.1327"), Decimal("15.1327"), Decimal("25.1327")]), (Decimal("0.1"), [Decimal("0.4"), Decimal("25.1"), Decimal("15.1"), Decimal("25.1")]), (Decimal(1), [Decimal("0"), Decimal("25"), Decimal("15"), Decimal("25")]), (Decimal(6), [Decimal("0"), Decimal("24"), Decimal("18"), Decimal("24")]), From 0a90fa6b6ed55335ee43de0c28585318b18fe622 Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:19:15 -0400 Subject: [PATCH 141/347] doc: Correct miscellaneous spelling mistakes in docstrings (#952) --- CHANGELOG.md | 2 +- doc/examples-adv-circuits-algorithms.rst | 2 +- doc/examples-braket-features.rst | 2 +- src/braket/annealing/problem.py | 2 +- src/braket/aws/aws_device.py | 2 +- src/braket/aws/aws_quantum_task.py | 2 +- src/braket/aws/aws_quantum_task_batch.py | 2 +- src/braket/aws/aws_session.py | 4 ++-- src/braket/circuits/circuit.py | 2 +- src/braket/circuits/gate_calibrations.py | 2 +- src/braket/circuits/moments.py | 2 +- src/braket/circuits/noise.py | 2 +- src/braket/circuits/noise_helpers.py | 8 ++++---- .../circuits/noise_model/qubit_initialization_criteria.py | 2 +- src/braket/circuits/quantum_operator_helpers.py | 2 +- .../text_diagram_builders/ascii_circuit_diagram.py | 2 +- .../text_diagram_builders/text_circuit_diagram.py | 4 ++-- .../text_diagram_builders/unicode_circuit_diagram.py | 2 +- src/braket/jobs/local/local_job_container.py | 2 +- .../analog_hamiltonian_simulation_quantum_task_result.py | 2 +- src/braket/timings/time_series.py | 2 +- src/braket/tracking/tracker.py | 4 ++-- 22 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3956a4da..8640a1db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ ### Bug Fixes and Other Changes - * backwards compatiblity for local detuning + * backwards compatibility for local detuning ## v1.76.1 (2024-04-08) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index b37e87e34..4a16c9cd6 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -2,7 +2,7 @@ Advanced circuits and algorithms ################################ -Learn more about working with advanced circuits and algoritms. +Learn more about working with advanced circuits and algorithms. .. toctree:: :maxdepth: 2 diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 75361f172..004e2bfe3 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -27,7 +27,7 @@ selection for your circuits manually, when running on QPUs. *************************************************************************************************************************************************************************************************** This example shows how to interact with the Amazon Braket GetDevice API to -retrieve Amazon Braket devices (such as simulators and QPUs) programatically, +retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, and how to gain access to their properties. *********************************************************************************************************************************************************************************** diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index d55de4e6e..9515cbfa6 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -39,7 +39,7 @@ def __init__( linear: dict[int, float] | None = None, quadratic: dict[tuple[int, int], float] | None = None, ): - """Initialzes a `Problem`. + """Initializes a `Problem`. Args: problem_type (ProblemType): The type of annealing problem diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 43780aa38..390145aec 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -819,7 +819,7 @@ def _parse_calibration_json( Returns: dict[tuple[Gate, QubitSet], PulseSequence]: The - structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration repesented as a + structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration represented as a `PulseSequence`. """ # noqa: E501 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 54f544159..c386469b0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -320,7 +320,7 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. If `use_cached_value` is `True`, Amazon Braket is not called and the most recently retrieved value is used, unless `GetQuantumTask` was never called, in which case - it wil still be called to populate the metadata for the first time. + it will still be called to populate the metadata for the first time. """ if not use_cached_value or not self._metadata: self._metadata = self._aws_session.get_quantum_task(self._arn) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 4d0d06b53..964fabe7e 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -432,7 +432,7 @@ def size(self) -> int: @property def unfinished(self) -> set[str]: - """Gets all the IDs of all the quantum tasks in teh batch that have yet to complete. + """Gets all the IDs of all the quantum tasks in the batch that have yet to complete. Returns: set[str]: The IDs of all the quantum tasks in the batch that have yet to complete. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index d2f2099f4..6a1bd1ce8 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -731,7 +731,7 @@ def describe_log_streams( Would have been received in a previous call. Returns: - dict[str, Any]: Dicionary containing logStreams and nextToken + dict[str, Any]: Dictionary containing logStreams and nextToken """ log_stream_args = { "logGroupName": log_group, @@ -767,7 +767,7 @@ def get_log_events( Would have been received in a previous call. Returns: - dict[str, Any]: Dicionary containing events, nextForwardToken, and nextBackwardToken + dict[str, Any]: Dictionary containing events, nextForwardToken, and nextBackwardToken """ log_events_args = { "logGroupName": log_group, diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 9bac39a6c..bf0ee07d8 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -276,7 +276,7 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. - ValueError: If a meaure instruction exists on the current circuit. + ValueError: If a measure instruction exists on the current circuit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 7617493cb..69ff66254 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -27,7 +27,7 @@ class GateCalibrations: - """An object containing gate calibration data. The data respresents the mapping on a particular gate + """An object containing gate calibration data. The data represents the mapping on a particular gate on a set of qubits to its calibration to be used by a quantum device. This is represented by a dictionary with keys of `Tuple(Gate, QubitSet)` mapped to a `PulseSequence`. """ # noqa: E501 diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 6622b3467..66ea41913 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -290,7 +290,7 @@ def _max_time_for_qubit(self, qubit: Qubit) -> int: return self._max_times.get(qubit, -1) # - # Implement abstract methods, default to calling selfs underlying dictionary + # Implement abstract methods, default to calling `self`'s underlying dictionary # def keys(self) -> KeysView[MomentsKey]: diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index fe256f1da..0cad544e2 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -639,7 +639,7 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """Initalizes a `DampingNoise`. + """Initializes a `DampingNoise`. Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 6dda8130d..89f30cac8 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -110,7 +110,7 @@ def check_noise_target_qubits( """Helper function to check whether all the target_qubits are positive integers. Args: - circuit (Circuit): A ciruit where `noise` is to be checked. + circuit (Circuit): A circuit where `noise` is to be checked. target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Returns: @@ -141,7 +141,7 @@ def apply_noise_to_moments( `target_qubits`. Args: - circuit (Circuit): A ciruit where `noise` is applied to. + circuit (Circuit): A circuit to `noise` is applied to. noise (Iterable[type[Noise]]): Noise channel(s) to be applied to the circuit. target_qubits (QubitSet): Index or indices of qubits. `noise` is applied to. @@ -209,7 +209,7 @@ def _apply_noise_to_gates_helper( Returns: tuple[Iterable[Instruction], int, bool]: A tuple of three values: - new_noise_instruction: A list of noise intructions + new_noise_instruction: A list of noise instructions noise_index: The number of noise channels applied to the gate noise_applied: Whether noise is applied or not """ @@ -248,7 +248,7 @@ def apply_noise_to_gates( the same number of qubits as `noise.qubit_count`. Args: - circuit (Circuit): A ciruit where `noise` is applied to. + circuit (Circuit): A circuit where `noise` is applied to. noise (Iterable[type[Noise]]): Noise channel(s) to be applied to the circuit. target_gates (Union[Iterable[type[Gate]], ndarray]): List of gates, or a unitary matrix diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index eb8ea0f21..e1790fd21 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -54,7 +54,7 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: Returns: Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type: - QUBIT will return a set of qubit targets that are relevant to this Critera, or + QUBIT will return a set of qubit targets that are relevant to this Criteria, or CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) qubits. All other keys will return an empty set. """ diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 8d64888ed..15cb8d1fd 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -88,7 +88,7 @@ def is_unitary(matrix: np.ndarray) -> bool: def is_cptp(matrices: Iterable[np.ndarray]) -> bool: - """Whether a transformation defined by these matrics as Kraus operators is a + """Whether a transformation defined by these matrices as Kraus operators is a completely positive trace preserving (CPTP) map. This is the requirement for a transformation to be a quantum channel. Reference: Section 8.2.3 in Nielsen & Chuang (2010) 10th edition. diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index 3106afc47..a633d318c 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -179,7 +179,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): character indicating if the gate also involve a qubit with a lower index. diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index c8bfa2650..e1dda5a3b 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -81,7 +81,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): specifies if a connection will be drawn above and/or below the box. @@ -229,7 +229,7 @@ def _create_output( qubits: QubitSet, global_phase: float | None, ) -> str: - """Creates the ouput for a single column: + """Creates the output for a single column: a. If there was one or more gphase gate, create a first line with the total global phase shift ending with the _vertical_delimiter() class attribute, e.g. 0.14| b. for each qubit, append the text representation produces by cls._draw_symbol diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 9e1779cb7..0739724e9 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -203,7 +203,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): specifies if a connection will be drawn above and/or below the box. diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index c4432dcf7..04aeaff1a 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -45,7 +45,7 @@ def __init__( Default: AwsSession() logger (Logger): Logger object with which to write logs. Default: `getLogger(__name__)` - force_update (bool): Try to update the container, if an update is availble. + force_update (bool): Try to update the container, if an update is available. Default: False """ self._aws_session = aws_session or AwsSession() diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index ade8a0f79..0bc110b2c 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -116,7 +116,7 @@ def get_counts(self) -> dict[str, int]: Returns: dict[str, int]: number of times each state configuration is measured. Returns None if none of shot measurements are successful. - Only succesful shots contribute to the state count. + Only successful shots contribute to the state count. """ state_counts = Counter() states = ["e", "r", "g"] diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index afdabd726..0023f32f5 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -152,7 +152,7 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: Notes: Keeps the time points in both time series unchanged. Assumes that the time points in the first TimeSeries - are at earler times then the time points in the second TimeSeries. + are at earlier times then the time points in the second TimeSeries. Returns: TimeSeries: The concatenated time series. diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 2f77eec66..47c13625a 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -122,7 +122,7 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: Returns: dict[str, dict[str, Any]]: A dictionary where each key is a device arn, and maps to - a dictionary sumarizing the quantum tasks run on the device. The summary includes the + a dictionary summarizing the quantum tasks run on the device. The summary includes the total shots sent to the device and the most recent status of the quantum tasks created on this device. For finished quantum tasks on simulator devices, the summary also includes the duration of the simulation. @@ -271,7 +271,7 @@ def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: product_family = "Simulator Task" operation = "CompleteTask" if details["status"] == "FAILED" and device_name == "TN1": - # Rehersal step of TN1 can fail and charges still apply. + # Rehearsal step of TN1 can fail and charges still apply. operation = "FailedTask" search_dict = { From 349957779432cf2b4efe02a907db976b197f21e4 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 16:19:17 +0000 Subject: [PATCH 142/347] prepare release v1.77.4 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8640a1db6..4731b0263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.77.4 (2024-04-16) + +### Bug Fixes and Other Changes + + * discretize method now takes None as an arg + +### Documentation Changes + + * Correct miscellaneous spelling mistakes in docstrings + ## v1.77.3.post0 (2024-04-15) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 292d3ff75..1b29b8f37 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4.dev0" +__version__ = "1.77.4" From 9388a25b448e91b28edcc19f774c4a41f6ed44f1 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 16:19:17 +0000 Subject: [PATCH 143/347] update development version to v1.77.5.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1b29b8f37..5d3392210 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4" +__version__ = "1.77.5.dev0" From f69c824da1122ad00640a88fd458ad433c108f11 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:34:57 -0700 Subject: [PATCH 144/347] fix: remove optional discretization fields (#953) --- src/braket/ahs/local_detuning.py | 4 ---- .../braket/ahs/test_analog_hamiltonian_simulation.py | 6 ++---- test/unit_tests/braket/ahs/test_local_detuning.py | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 1906ce837..39a475922 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -154,11 +154,7 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: """ local_detuning_parameters = properties.rydberg.rydbergLocal time_resolution = local_detuning_parameters.timeResolution - value_resolution = local_detuning_parameters.commonDetuningResolution - pattern_resolution = local_detuning_parameters.localDetuningResolution discretized_magnitude = self.magnitude.discretize( time_resolution=time_resolution, - value_resolution=value_resolution, - pattern_resolution=pattern_resolution, ) return LocalDetuning(discretized_magnitude) diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index 65cc2a074..83178c120 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -141,8 +141,6 @@ def test_discretize(register, driving_field, local_detuning): device.properties.paradigm.rydberg.rydbergGlobal.phaseResolution = Decimal("5E-7") device.properties.paradigm.rydberg.rydbergLocal.timeResolution = Decimal("1E-9") - device.properties.paradigm.rydberg.rydbergLocal.commonDetuningResolution = Decimal("2000.0") - device.properties.paradigm.rydberg.rydbergLocal.localDetuningResolution = Decimal("0.01") discretized_ahs = ahs.discretize(device) discretized_ir = discretized_ahs.to_ir() @@ -179,10 +177,10 @@ def test_discretize(register, driving_field, local_detuning): } local_detuning = discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] assert local_detuning == { - "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], + "pattern": ["0.5", "1", "0.5", "0.5", "0.5", "0.5"], "time_series": { "times": ["0E-9", "0.000003000"], - "values": ["-125664000.0", "125664000.0"], + "values": ["-125664000", "125664000"], }, } diff --git a/test/unit_tests/braket/ahs/test_local_detuning.py b/test/unit_tests/braket/ahs/test_local_detuning.py index 4c4f94675..8768dce64 100644 --- a/test/unit_tests/braket/ahs/test_local_detuning.py +++ b/test/unit_tests/braket/ahs/test_local_detuning.py @@ -129,8 +129,6 @@ def test_discretize(): discretized_field = field.discretize(mock_properties) magnitude_mock.discretize.assert_called_with( time_resolution=mock_properties.rydberg.rydbergLocal.timeResolution, - value_resolution=mock_properties.rydberg.rydbergLocal.commonDetuningResolution, - pattern_resolution=mock_properties.rydberg.rydbergLocal.localDetuningResolution, ) assert field is not discretized_field assert discretized_field.magnitude == magnitude_mock.discretize.return_value From f66a21a11785261852e413477ac76ac0296d1ff5 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 20:53:03 +0000 Subject: [PATCH 145/347] prepare release v1.77.5 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4731b0263..2a86ca359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.5 (2024-04-16) + +### Bug Fixes and Other Changes + + * remove optional discretization fields + ## v1.77.4 (2024-04-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5d3392210..6ce89b9b2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.5.dev0" +__version__ = "1.77.5" From 0e22849e194f2844269eb0f0ddd7c6a06c91c6d7 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 20:53:03 +0000 Subject: [PATCH 146/347] update development version to v1.77.6.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6ce89b9b2..15eb83029 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.5" +__version__ = "1.77.6.dev0" From 3698931d5015533f4600cfc7d4c7d856f31c5b42 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:53:40 -0700 Subject: [PATCH 147/347] fix: if rydberg local is not pulled, pass in None (#959) --- src/braket/ahs/local_detuning.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 39a475922..59732d4ff 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -153,7 +153,9 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: LocalDetuning: A new discretized LocalDetuning. """ local_detuning_parameters = properties.rydberg.rydbergLocal - time_resolution = local_detuning_parameters.timeResolution + time_resolution = ( + local_detuning_parameters.timeResolution if local_detuning_parameters else None + ) discretized_magnitude = self.magnitude.discretize( time_resolution=time_resolution, ) From c3eb8b619c486b7445ed9b43a514775e82385eee Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Apr 2024 18:18:35 +0000 Subject: [PATCH 148/347] prepare release v1.77.6 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a86ca359..6a91e3d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.6 (2024-04-17) + +### Bug Fixes and Other Changes + + * if rydberg local is not pulled, pass in None + ## v1.77.5 (2024-04-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 15eb83029..79184888d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.6.dev0" +__version__ = "1.77.6" From 9bda24493641d8ab783333849dd0268adb214087 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Apr 2024 18:18:35 +0000 Subject: [PATCH 149/347] update development version to v1.77.7.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 79184888d..07aeada44 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.6" +__version__ = "1.77.7.dev0" From 0cd45310542265fc41b517fb11626fdb51b82e37 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:02:09 -0700 Subject: [PATCH 150/347] feat: add phase RX gate (#945) --- .coveragerc | 6 + src/braket/circuits/angled_gate.py | 23 ++-- src/braket/circuits/gates.py | 119 ++++++++++++++++++ src/braket/circuits/translations.py | 1 + .../braket/circuits/test_circuit.py | 1 + test/unit_tests/braket/circuits/test_gates.py | 42 ++++++- 6 files changed, 178 insertions(+), 14 deletions(-) diff --git a/.coveragerc b/.coveragerc index 79545ce2b..933315e86 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,9 +23,15 @@ exclude_lines = # Have to re-enable the standard pragma pragma: no cover + # Skipping import testing + from importlib.metadata import entry_points + # Don't complain if tests don't hit defensive assertion code: raise NotImplementedError + # Avoid situation where system version causes coverage issues + if sys.version_info.minor == 9: + [html] directory = build/coverage diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 5b4c4faba..49447e58f 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -433,26 +433,27 @@ def get_angle(gate: AngledGate, **kwargs: FreeParameterExpression | str) -> Angl def _get_angles( - gate: TripleAngledGate, **kwargs: FreeParameterExpression | str -) -> TripleAngledGate: + gate: DoubleAngledGate | TripleAngledGate, **kwargs: FreeParameterExpression | str +) -> DoubleAngledGate | TripleAngledGate: """Gets the angle with all values substituted in that are requested. Args: - gate (TripleAngledGate): The subclass of TripleAngledGate for which the angle is being - obtained. + gate (DoubleAngledGate | TripleAngledGate): The subclass of multi angle AngledGate for + which the angle is being obtained. **kwargs (FreeParameterExpression | str): The named parameters that are being filled for a particular gate. Returns: - TripleAngledGate: A new gate of the type of the AngledGate originally used with all angles - updated. + DoubleAngledGate | TripleAngledGate: A new gate of the type of the AngledGate + originally used with all angles updated. """ - new_angles = [ - ( + angles = [f"angle_{i + 1}" for i in range(len(gate._parameters))] + new_angles_args = { + angle: ( getattr(gate, angle).subs(kwargs) if isinstance(getattr(gate, angle), FreeParameterExpression) else getattr(gate, angle) ) - for angle in ("angle_1", "angle_2", "angle_3") - ] - return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1], angle_3=new_angles[2]) + for angle in angles + } + return type(gate)(**new_angles_args) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 84ff3de05..bc4c2f9e9 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -24,6 +24,7 @@ from braket.circuits import circuit from braket.circuits.angled_gate import ( AngledGate, + DoubleAngledGate, TripleAngledGate, _get_angles, _multi_angled_ascii_characters, @@ -3298,6 +3299,124 @@ def gpi( Gate.register_gate(GPi) +class PRx(DoubleAngledGate): + r"""Phase Rx gate. + + Unitary matrix: + + .. math:: \mathtt{PRx}(\theta,\phi) = \begin{bmatrix} + \cos{(\theta / 2)} & -i e^{-i \phi} \sin{(\theta / 2)} \\ + -i e^{i \phi} \sin{(\theta / 2)} & \cos{(\theta / 2)} + \end{bmatrix}. + + Args: + angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in + radians or expression representation. + angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in + radians or expression representation. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + ): + super().__init__( + angle_1=angle_1, + angle_2=angle_2, + qubit_count=None, + ascii_symbols=[_multi_angled_ascii_characters("PRx", angle_1, angle_2)], + ) + + @property + def _qasm_name(self) -> str: + return "prx" + + def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + + Returns: + np.ndarray: The matrix representation of this gate. + """ + theta = self.angle_1 + phi = self.angle_2 + return np.array( + [ + [ + np.cos(theta / 2), + -1j * np.exp(-1j * phi) * np.sin(theta / 2), + ], + [ + -1j * np.exp(1j * phi) * np.sin(theta / 2), + np.cos(theta / 2), + ], + ] + ) + + def adjoint(self) -> list[Gate]: + return [PRx(-self.angle_1, self.angle_2)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs) -> PRx: + return _get_angles(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def prx( + target: QubitSetInput, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: + r"""PhaseRx gate. + + .. math:: \mathtt{PRx}(\theta,\phi) = \begin{bmatrix} + \cos{(\theta / 2)} & -i e^{-i \phi} \sin{(\theta / 2)} \\ + -i e^{i \phi} \sin{(\theta / 2)} & \cos{(\theta / 2)} + \end{bmatrix}. + + Args: + target (QubitSetInput): Target qubit(s). + angle_1 (Union[FreeParameterExpression, float]): First angle in radians. + angle_2 (Union[FreeParameterExpression, float]): Second angle in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Iterable[Instruction]: PhaseRx instruction. + + Examples: + >>> circ = Circuit().prx(0, 0.15, 0.25) + """ + return [ + Instruction( + PRx(angle_1, angle_2), + target=qubit, + control=control, + control_state=control_state, + power=power, + ) + for qubit in QubitSet(target) + ] + + +Gate.register_gate(PRx) + + class GPi2(AngledGate): r"""IonQ GPi2 gate. diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 9537460ef..78bb7eed0 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -67,6 +67,7 @@ "cswap": braket_gates.CSwap, "gpi": braket_gates.GPi, "gpi2": braket_gates.GPi2, + "prx": braket_gates.PRx, "ms": braket_gates.MS, "unitary": braket_gates.Unitary, } diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index b29f3b995..c0c58ad35 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -2536,6 +2536,7 @@ def test_to_unitary_with_global_phase(): (Circuit().cphaseshift00(0, 1, 0.15), gates.CPhaseShift00(0.15).to_matrix()), (Circuit().cphaseshift01(0, 1, 0.15), gates.CPhaseShift01(0.15).to_matrix()), (Circuit().cphaseshift10(0, 1, 0.15), gates.CPhaseShift10(0.15).to_matrix()), + (Circuit().prx(0, 1, 0.15), gates.PRx(1, 0.15).to_matrix()), (Circuit().cy(0, 1), gates.CY().to_matrix()), (Circuit().cz(0, 1), gates.CZ().to_matrix()), (Circuit().xx(0, 1, 0.15), gates.XX(0.15).to_matrix()), diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 1b6ac56c7..3a7e621df 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -39,6 +39,10 @@ class NoTarget: pass +class DoubleAngle: + pass + + class TripleAngle: pass @@ -103,6 +107,7 @@ class SingleNegControlModifier: (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), (Gate.GPi, "gpi", None, [SingleTarget, Angle], {}), (Gate.GPi2, "gpi2", None, [SingleTarget, Angle], {}), + (Gate.PRx, "prx", None, [SingleTarget, DoubleAngle], {}), (Gate.MS, "ms", None, [DoubleTarget, TripleAngle], {}), ( Gate.Unitary, @@ -145,9 +150,11 @@ class SingleNegControlModifier: Gate.CPhaseShift10, Gate.GPi, Gate.GPi2, + Gate.PRx, Gate.MS, ] + invalid_unitary_matrices = [ (np.array([[1]])), (np.array([1])), @@ -179,6 +186,10 @@ def angle_valid_input(**kwargs): return {"angle": 0.123} +def double_angle_valid_input(**kwargs): + return {"angle_1": 0.123, "angle_2": 3.567} + + def triple_angle_valid_input(**kwargs): return {"angle_1": 0.123, "angle_2": 4.567, "angle_3": 8.910} @@ -217,6 +228,7 @@ def two_dimensional_matrix_valid_input(**kwargs): "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, + "DoubleAngle": double_angle_valid_input, "TripleAngle": triple_angle_valid_input, "SingleControl": single_control_valid_input, "SingleNegControlModifier": single_neg_control_valid_input, @@ -273,7 +285,7 @@ def create_valid_target_input(irsubclasses): control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -287,6 +299,8 @@ def create_valid_gate_class_input(irsubclasses, **kwargs): input = {} if Angle in irsubclasses: input.update(angle_valid_input()) + if DoubleAngle in irsubclasses: + input.update(double_angle_valid_input()) if TripleAngle in irsubclasses: input.update(triple_angle_valid_input()) if TwoDimensionalMatrix in irsubclasses: @@ -313,7 +327,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -847,6 +861,18 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), "gpi2(0.17) $4;", ), + ( + Gate.PRx(angle_1=0.17, angle_2=3.45), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "prx(0.17, 3.45) q[4];", + ), + ( + Gate.PRx(angle_1=0.17, angle_2=3.45), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "prx(0.17, 3.45) $4;", + ), ( Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], @@ -902,6 +928,8 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar subroutine_input = {"target": multi_targets} if Angle in irsubclasses: subroutine_input.update(angle_valid_input()) + if DoubleAngle in irsubclasses: + subroutine_input.update(double_angle_valid_input()) if TripleAngle in irsubclasses: subroutine_input.update(triple_angle_valid_input()) assert subroutine(**subroutine_input) == Circuit(instruction_list) @@ -1012,8 +1040,13 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): + double_angled = gate.__name__ in ["PRx"] triple_angled = gate.__name__ in ("MS", "U") - num_params = 3 if triple_angled else 1 + num_params = 1 + if triple_angled: + num_params = 3 + elif double_angled: + num_params = 2 thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] mapping = {f"theta_{i}": i for i in range(num_params)} param_gate = gate(*thetas) @@ -1024,6 +1057,9 @@ def test_bind_values(gate): if triple_angled: for angle in new_gate.angle_1, new_gate.angle_2, new_gate.angle_3: assert isinstance(angle, float) + elif double_angled: + for angle in new_gate.angle_1, new_gate.angle_2: + assert isinstance(angle, float) else: assert isinstance(new_gate.angle, float) From b4920314817b5c8b72f5d4e5e616cebf65484644 Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:36:54 -0400 Subject: [PATCH 151/347] Feature: Code refactoring with Sourcery (#954) * fix: update to python 39 syntax * fix: sourcery corrections fix: line too long error * fix: tests * fix: tests * fix: tests * Update src/braket/circuits/circuit.py Co-authored-by: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> * fix: update readme, delete .yaml --------- Co-authored-by: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> --- .coveragerc | 2 +- .github/dependabot.yml | 1 - .readthedocs.yml | 2 +- CONTRIBUTING.md | 4 +- README.md | 17 ++--- doc/conf.py | 4 +- doc/examples-adv-circuits-algorithms.rst | 40 +++++------ doc/examples-braket-features.rst | 14 ++-- doc/examples-getting-started.rst | 39 +++++----- doc/examples-hybrid-quantum.rst | 10 +-- doc/examples-ml-pennylane.rst | 30 ++++---- doc/examples.rst | 4 +- doc/getting-started.rst | 5 +- doc/index.rst | 7 +- setup.cfg | 4 +- setup.py | 2 +- src/braket/ahs/atom_arrangement.py | 2 +- src/braket/ahs/discretization_types.py | 2 - src/braket/ahs/local_detuning.py | 2 +- src/braket/aws/aws_device.py | 71 +++++++++---------- src/braket/aws/aws_quantum_job.py | 34 ++++----- src/braket/aws/aws_quantum_task.py | 55 ++++++-------- src/braket/aws/aws_quantum_task_batch.py | 9 +-- src/braket/aws/aws_session.py | 27 ++++--- src/braket/circuits/circuit.py | 34 ++++----- src/braket/circuits/gates.py | 11 ++- src/braket/circuits/moments.py | 6 +- src/braket/circuits/noise.py | 16 ++--- src/braket/circuits/noise_helpers.py | 4 +- .../circuit_instruction_criteria.py | 4 +- src/braket/circuits/noise_model/criteria.py | 8 +-- .../noise_model/criteria_input_parsing.py | 4 +- .../circuits/noise_model/noise_model.py | 5 +- .../noise_model/observable_criteria.py | 4 +- .../qubit_initialization_criteria.py | 4 +- src/braket/circuits/noises.py | 18 ++--- src/braket/circuits/observables.py | 21 +++--- src/braket/circuits/quantum_operator.py | 10 +-- .../circuits/quantum_operator_helpers.py | 2 +- src/braket/circuits/result_types.py | 12 +--- .../ascii_circuit_diagram.py | 14 ++-- .../text_circuit_diagram.py | 2 +- .../text_circuit_diagram_utils.py | 9 +-- .../unicode_circuit_diagram.py | 11 +-- src/braket/devices/device.py | 6 +- src/braket/devices/local_simulator.py | 15 ++-- src/braket/ipython_utils.py | 7 +- src/braket/jobs/environment_variables.py | 4 +- src/braket/jobs/hybrid_job.py | 8 +-- src/braket/jobs/local/local_job.py | 15 ++-- src/braket/jobs/local/local_job_container.py | 4 +- .../jobs/local/local_job_container_setup.py | 8 ++- src/braket/jobs/logs.py | 8 +-- .../cwl_insights_metrics_fetcher.py | 3 +- .../jobs/metrics_data/cwl_metrics_fetcher.py | 18 ++--- src/braket/jobs/metrics_data/exceptions.py | 2 - .../jobs/metrics_data/log_metrics_parser.py | 3 +- src/braket/jobs/quantum_job_creation.py | 54 +++++++------- src/braket/parametric/free_parameter.py | 4 +- src/braket/pulse/ast/approximation_parser.py | 22 +++--- src/braket/pulse/ast/free_parameters.py | 10 +-- src/braket/pulse/ast/qasm_transformer.py | 33 +++++---- src/braket/pulse/pulse_sequence.py | 63 ++++++++-------- src/braket/pulse/waveforms.py | 7 +- .../quantum_information/pauli_string.py | 2 +- src/braket/registers/qubit.py | 5 +- ...iltonian_simulation_quantum_task_result.py | 2 +- .../tasks/gate_model_quantum_task_result.py | 25 +++---- src/braket/timings/time_series.py | 2 +- .../gate_model_device_testing_utils.py | 62 ++++++++-------- test/integ_tests/job_test_script.py | 2 +- .../test_create_local_quantum_job.py | 2 +- test/integ_tests/test_create_quantum_job.py | 19 +++-- test/integ_tests/test_device_creation.py | 12 ++-- .../braket/ahs/test_atom_arrangement.py | 4 +- .../braket/aws/common_test_utils.py | 6 +- test/unit_tests/braket/aws/test_aws_device.py | 30 ++++---- .../braket/aws/test_aws_quantum_job.py | 4 +- .../braket/aws/test_aws_quantum_task.py | 18 ++--- .../unit_tests/braket/aws/test_aws_session.py | 8 +-- .../braket/circuits/test_angled_gate.py | 2 +- .../braket/circuits/test_circuit.py | 35 +++------ test/unit_tests/braket/circuits/test_gates.py | 36 +++++----- .../braket/circuits/test_instruction.py | 13 ++-- .../braket/circuits/test_moments.py | 2 +- .../unit_tests/braket/circuits/test_noises.py | 38 +++++----- .../braket/circuits/test_observable.py | 2 +- .../braket/circuits/test_observables.py | 14 ++-- .../braket/circuits/test_quantum_operator.py | 2 +- .../braket/devices/test_local_simulator.py | 10 +-- .../metrics_data/test_cwl_metrics_fetcher.py | 4 +- .../braket/jobs/test_data_persistence.py | 4 +- .../unit_tests/braket/jobs/test_hybrid_job.py | 2 +- .../braket/jobs/test_quantum_job_creation.py | 24 +++---- .../pulse/ast/test_approximation_parser.py | 26 +++---- .../unit_tests/braket/pulse/test_waveforms.py | 10 +-- .../unit_tests/braket/registers/test_qubit.py | 2 +- .../braket/registers/test_qubit_set.py | 8 +-- .../test_annealing_quantum_task_result.py | 2 +- .../test_gate_model_quantum_task_result.py | 2 +- .../braket/tasks/test_local_quantum_task.py | 2 +- .../braket/timings/test_time_series.py | 8 +-- 102 files changed, 595 insertions(+), 736 deletions(-) diff --git a/.coveragerc b/.coveragerc index 933315e86..fe9662c0a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,7 +3,7 @@ parallel = True branch = True source = src -omit = +omit = **/braket/ir/* **/braket/device_schema/* **/braket/schema_common/* diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed79a0d63..04595aed1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,4 +10,3 @@ updates: interval: "weekly" commit-message: prefix: infra - diff --git a/.readthedocs.yml b/.readthedocs.yml index e824a6afc..b6ca23199 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -12,7 +12,7 @@ sphinx: # Optionally build your docs in additional formats such as PDF formats: - pdf - + # setting up build.os and the python version build: os: ubuntu-22.04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7362a17b7..0df22f51e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -218,9 +218,9 @@ You can then find the generated HTML files in `build/documentation/html`. Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amazon-braket/amazon-braket-sdk-python/labels/help%20wanted) issues is a great place to start. ## Building Integrations -The Amazon Braket SDK supports integrations with popular quantum computing frameworks such as [PennyLane](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/amazon-braket/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/amazon-braket/amazon-braket-ocean-plugin-python). These serve as a good reference for a new integration you wish to develop. +The Amazon Braket SDK supports integrations with popular quantum computing frameworks such as [PennyLane](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/amazon-braket/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/amazon-braket/amazon-braket-ocean-plugin-python). These serve as a good reference for a new integration you wish to develop. -When developing a new integration with the Amazon Braket SDK, please remember to update the [user agent header](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3) to include version information for your integration. An example can be found [here](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python/commit/ccee35604afc2b04d83ee9103eccb2821a4256cb). +When developing a new integration with the Amazon Braket SDK, please remember to update the [user agent header](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3) to include version information for your integration. An example can be found [here](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python/commit/ccee35604afc2b04d83ee9103eccb2821a4256cb). ## Code of Conduct diff --git a/README.md b/README.md index 1c79e8034..b03bd1ef4 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ Many quantum algorithms need to run multiple independent circuits, and submittin ```python circuits = [bell for _ in range(5)] batch = device.run_batch(circuits, shots=100) -print(batch.results()[0].measurement_counts) # The result of the first quantum task in the batch +# The result of the first quantum task in the batch +print(batch.results()[0].measurement_counts) ``` ### Running a hybrid job @@ -139,14 +140,14 @@ from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell) +task = device.run(bell) print(task.result().measurement_counts) ``` When you execute your task, Amazon Braket polls for a result. By default, Braket polls for 5 days; however, it is possible to change this by modifying the `poll_timeout_seconds` parameter in `AwsDevice.run`, as in the example below. Keep in mind that if your polling timeout is too short, results may not be returned within the polling time, such as when a QPU is unavailable, and a local timeout error is returned. You can always restart the polling by using `task.result()`. ```python -task = device.run(bell, poll_timeout_seconds=86400) # 1 day +task = device.run(bell, poll_timeout_seconds=86400) # 1 day print(task.result().measurement_counts) ``` @@ -232,15 +233,15 @@ tox -e integ-tests -- your-arguments ### Issues and Bug Reports -If you encounter bugs or face issues while using the SDK, please let us know by posting -the issue on our [Github issue tracker](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/). +If you encounter bugs or face issues while using the SDK, please let us know by posting +the issue on our [Github issue tracker](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/). For other issues or general questions, please ask on the [Quantum Computing Stack Exchange](https://quantumcomputing.stackexchange.com/questions/ask?Tags=amazon-braket). ### Feedback and Feature Requests -If you have feedback or features that you would like to see on Amazon Braket, we would love to hear from you! -[Github issues](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/) is our preferred mechanism for collecting feedback and feature requests, allowing other users -to engage in the conversation, and +1 issues to help drive priority. +If you have feedback or features that you would like to see on Amazon Braket, we would love to hear from you! +[Github issues](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/) is our preferred mechanism for collecting feedback and feature requests, allowing other users +to engage in the conversation, and +1 issues to help drive priority. ### Code contributors diff --git a/doc/conf.py b/doc/conf.py index a2548fc65..8a16ca231 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,7 +7,7 @@ project = "amazon-braket-sdk" version = version(project) release = version -copyright = "{}, Amazon.com".format(datetime.datetime.now().year) +copyright = f"{datetime.datetime.now().year}, Amazon.com" extensions = [ "sphinxcontrib.apidoc", @@ -26,7 +26,7 @@ default_role = "py:obj" html_theme = "sphinx_rtd_theme" -htmlhelp_basename = "{}doc".format(project) +htmlhelp_basename = f"{project}doc" language = "en" diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 4a16c9cd6..23de8a047 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -6,26 +6,26 @@ Learn more about working with advanced circuits and algorithms. .. toctree:: :maxdepth: 2 - + ********************************************************************************************************************************************************** `Grover's search algorithm `_ ********************************************************************************************************************************************************** -This tutorial provides a step-by-step walkthrough of Grover's quantum algorithm. -You learn how to build the corresponding quantum circuit with simple modular building -blocks using the Amazon Braket SDK. You will learn how to build custom -gates that are not part of the basic gate set provided by the SDK. A custom gate can used +This tutorial provides a step-by-step walkthrough of Grover's quantum algorithm. +You learn how to build the corresponding quantum circuit with simple modular building +blocks using the Amazon Braket SDK. You will learn how to build custom +gates that are not part of the basic gate set provided by the SDK. A custom gate can used as a core quantum gate by registering it as a subroutine. ****************************************************************************************************************************************************************************************************************** `Quantum amplitude amplification `_ ****************************************************************************************************************************************************************************************************************** -This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) -algorithm using the Amazon Braket SDK. QAA is a routine in quantum computing which generalizes the idea behind -Grover's famous search algorithm, with applications across many quantum algorithms. QAA uses an iterative -approach to systematically increase the probability of finding one or multiple -target states in a given search space. In a quantum computer, QAA can be used to obtain a +This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) +algorithm using the Amazon Braket SDK. QAA is a routine in quantum computing which generalizes the idea behind +Grover's famous search algorithm, with applications across many quantum algorithms. QAA uses an iterative +approach to systematically increase the probability of finding one or multiple +target states in a given search space. In a quantum computer, QAA can be used to obtain a quadratic speedup over several classical algorithms. @@ -33,18 +33,18 @@ quadratic speedup over several classical algorithms. `Quantum Fourier transform `_ ************************************************************************************************************************************************************************************************ -This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and -its inverse using Amazon Braket's SDK. The QFT is an important subroutine to many quantum algorithms, -most famously Shor's algorithm for factoring and the quantum phase estimation (QPE) algorithm -for estimating the eigenvalues of a unitary operator. +This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and +its inverse using Amazon Braket's SDK. The QFT is an important subroutine to many quantum algorithms, +most famously Shor's algorithm for factoring and the quantum phase estimation (QPE) algorithm +for estimating the eigenvalues of a unitary operator. ********************************************************************************************************************************************************************************************* `Quantum phase estimation `_ ********************************************************************************************************************************************************************************************* -This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) -algorithm using the Amazon Braket SDK. The QPE algorithm is designed to estimate the -eigenvalues of a unitary operator. Eigenvalue problems can be found across many -disciplines and application areas, including principal component analysis (PCA) -as used in machine learning and the solution of differential equations in mathematics, physics, -engineering and chemistry. +This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) +algorithm using the Amazon Braket SDK. The QPE algorithm is designed to estimate the +eigenvalues of a unitary operator. Eigenvalue problems can be found across many +disciplines and application areas, including principal component analysis (PCA) +as used in machine learning and the solution of differential equations in mathematics, physics, +engineering and chemistry. diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 004e2bfe3..25c088ab1 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -11,30 +11,30 @@ Learn more about the indivudal features of Amazon Braket. `Getting notifications when a quantum task completes `_ ***************************************************************************************************************************************************************************************************************************************************************** -This tutorial illustrates how Amazon Braket integrates with Amazon EventBridge for -event-based processing. In the tutorial, you will learn how to configure Amazon Braket -and Amazon Eventbridge to receive text notification about quantum task completions on your phone. +This tutorial illustrates how Amazon Braket integrates with Amazon EventBridge for +event-based processing. In the tutorial, you will learn how to configure Amazon Braket +and Amazon Eventbridge to receive text notification about quantum task completions on your phone. *********************************************************************************************************************************************************************** `Allocating Qubits on QPU Devices `_ *********************************************************************************************************************************************************************** -This tutorial explains how you can use the Amazon Braket SDK to allocate the qubit +This tutorial explains how you can use the Amazon Braket SDK to allocate the qubit selection for your circuits manually, when running on QPUs. *************************************************************************************************************************************************************************************************** `Getting Devices and Checking Device Properties `_ *************************************************************************************************************************************************************************************************** -This example shows how to interact with the Amazon Braket GetDevice API to -retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, +This example shows how to interact with the Amazon Braket GetDevice API to +retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, and how to gain access to their properties. *********************************************************************************************************************************************************************************** `Using the tensor network simulator TN1 `_ *********************************************************************************************************************************************************************************** -This notebook introduces the Amazon Braket managed tensor network simulator, TN1. +This notebook introduces the Amazon Braket managed tensor network simulator, TN1. You will learn about how TN1 works, how to use it, and which problems are best suited to run on TN1. diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index 8c9eb90f5..64c6939af 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -6,7 +6,7 @@ Get started on Amazon Braket with some introductory examples. .. toctree:: :maxdepth: 2 - + ********************************************************************************************************************************************************* `Getting started `_ ********************************************************************************************************************************************************* @@ -17,11 +17,11 @@ A hello-world tutorial that shows you how to build a simple circuit and run it o `Running quantum circuits on simulators `_ ****************************************************************************************************************************************************************************************************************************** -This tutorial prepares a paradigmatic example for a multi-qubit entangled state, -the so-called GHZ state (named after the three physicists Greenberger, Horne, and Zeilinger). -The GHZ state is extremely non-classical, and therefore very sensitive to decoherence. -It is often used as a performance benchmark for today's hardware. In many quantum information -protocols it is used as a resource for quantum error correction, quantum communication, +This tutorial prepares a paradigmatic example for a multi-qubit entangled state, +the so-called GHZ state (named after the three physicists Greenberger, Horne, and Zeilinger). +The GHZ state is extremely non-classical, and therefore very sensitive to decoherence. +It is often used as a performance benchmark for today's hardware. In many quantum information +protocols it is used as a resource for quantum error correction, quantum communication, and quantum metrology. **Note:** When a circuit is ran using a simulator, customers are required to use contiguous qubits/indices. @@ -30,30 +30,29 @@ and quantum metrology. `Running quantum circuits on QPU devices `_ ********************************************************************************************************************************************************************************************************************************* -This tutorial prepares a maximally-entangled Bell state between two qubits, -for classical simulators and for QPUs. For classical devices, we can run the circuit on a -local simulator or a cloud-based managed simulator. For the quantum devices, -we run the circuit on the superconducting machine from Rigetti, and on the ion-trap -machine provided by IonQ. +This tutorial prepares a maximally-entangled Bell state between two qubits, +for classical simulators and for QPUs. For classical devices, we can run the circuit on a +local simulator or a cloud-based managed simulator. For the quantum devices, +we run the circuit on the superconducting machine from Rigetti, and on the ion-trap +machine provided by IonQ. ****************************************************************************************************************************************************************************************************************************************************** `Deep Dive into the anatomy of quantum circuits `_ ****************************************************************************************************************************************************************************************************************************************************** -This tutorial discusses in detail the anatomy of quantum circuits in the Amazon -Braket SDK. You will learn how to build (parameterized) circuits and display them +This tutorial discusses in detail the anatomy of quantum circuits in the Amazon +Braket SDK. You will learn how to build (parameterized) circuits and display them graphically, and how to append circuits to each other. Next, learn -more about circuit depth and circuit size. Finally you will learn how to execute -the circuit on a device of our choice (defining a quantum task) and how to track, log, +more about circuit depth and circuit size. Finally you will learn how to execute +the circuit on a device of our choice (defining a quantum task) and how to track, log, recover, or cancel a quantum task efficiently. *************************************************************************************************************************************************************** `Superdense coding `_ *************************************************************************************************************************************************************** -This tutorial constructs an implementation of the superdense coding protocol using -the Amazon Braket SDK. Superdense coding is a method of transmitting two classical -bits by sending only one qubit. Starting with a pair of entanged qubits, the sender -(aka Alice) applies a certain quantum gate to their qubit and sends the result +This tutorial constructs an implementation of the superdense coding protocol using +the Amazon Braket SDK. Superdense coding is a method of transmitting two classical +bits by sending only one qubit. Starting with a pair of entanged qubits, the sender +(aka Alice) applies a certain quantum gate to their qubit and sends the result to the receiver (aka Bob), who is then able to decode the full two-bit message. - diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index 9c7f3aca2..9a0a8efba 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -11,19 +11,19 @@ Learn more about hybrid quantum algorithms. `QAOA `_ ************************************************************************************************************************************* -This tutorial shows how to (approximately) solve binary combinatorial optimization problems -using the Quantum Approximate Optimization Algorithm (QAOA). +This tutorial shows how to (approximately) solve binary combinatorial optimization problems +using the Quantum Approximate Optimization Algorithm (QAOA). ************************************************************************************************************************************************************************************ `VQE Transverse Ising `_ ************************************************************************************************************************************************************************************ This tutorial shows how to solve for the ground state of the Transverse Ising Model -using the variational quantum eigenvalue solver (VQE). +using the variational quantum eigenvalue solver (VQE). **************************************************************************************************************************************************************** `VQE Chemistry `_ **************************************************************************************************************************************************************** -This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in -Amazon Braket SDK to compute the potential energy surface (PES) for the Hydrogen molecule. +This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in +Amazon Braket SDK to compute the potential energy surface (PES) for the Hydrogen molecule. diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index 5c7db93aa..1aa57cc4c 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -11,37 +11,37 @@ Learn more about how to combine PennyLane with Amazon Braket. `Combining PennyLane with Amazon Braket `_ ************************************************************************************************************************************************************************** -This tutorial shows you how to construct circuits and evaluate their gradients in +This tutorial shows you how to construct circuits and evaluate their gradients in PennyLane with execution performed using Amazon Braket. ***************************************************************************************************************************************************************************************************************************************************** `Computing gradients in parallel with PennyLane-Braket `_ ***************************************************************************************************************************************************************************************************************************************************** -Learn how to speed up training of quantum circuits by using parallel execution on -Amazon Braket. Quantum circuit training involving gradients -requires multiple device executions. The Amazon Braket SV1 simulator can be used to overcome this. -The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperforms the -local simulator for both executions and gradient calculations. This illustrates how +Learn how to speed up training of quantum circuits by using parallel execution on +Amazon Braket. Quantum circuit training involving gradients +requires multiple device executions. The Amazon Braket SV1 simulator can be used to overcome this. +The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperforms the +local simulator for both executions and gradient calculations. This illustrates how parallel capabilities can be combined between PennyLane and SV1. ****************************************************************************************************************************************************************************************** `Graph optimization with QAOA `_ ****************************************************************************************************************************************************************************************** -In this tutorial, you learn how quantum circuit training can be applied to a problem -of practical relevance in graph optimization. It easy it is to train a QAOA circuit in -PennyLane to solve the maximum clique problem on a simple example graph. The tutorial -then extends to a more difficult 20-node graph and uses the parallel capabilities of -the Amazon Braket SV1 simulator to speed up gradient calculations and hence train the quantum circuit faster, +In this tutorial, you learn how quantum circuit training can be applied to a problem +of practical relevance in graph optimization. It easy it is to train a QAOA circuit in +PennyLane to solve the maximum clique problem on a simple example graph. The tutorial +then extends to a more difficult 20-node graph and uses the parallel capabilities of +the Amazon Braket SV1 simulator to speed up gradient calculations and hence train the quantum circuit faster, using around 1-2 minutes per iteration. *************************************************************************************************************************************************************************************************************** `Hydrogen Molecule geometry with VQE `_ *************************************************************************************************************************************************************************************************************** -In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an -important problem in quantum chemistry. The ground state energy of molecular hydrogen is calculated -by optimizing a VQE circuit using the local Braket simulator. This tutorial highlights how -qubit-wise commuting observables can be measured together in PennyLane and Amazon Braket, +In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an +important problem in quantum chemistry. The ground state energy of molecular hydrogen is calculated +by optimizing a VQE circuit using the local Braket simulator. This tutorial highlights how +qubit-wise commuting observables can be measured together in PennyLane and Amazon Braket, making optimization more efficient. diff --git a/doc/examples.rst b/doc/examples.rst index 87c2e1f7a..93aac757b 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,7 +1,7 @@ ######## Examples ######## - + There are several examples available in the Amazon Braket repo: https://github.com/amazon-braket/amazon-braket-examples. @@ -14,5 +14,3 @@ https://github.com/amazon-braket/amazon-braket-examples. examples-hybrid-quantum.rst examples-ml-pennylane.rst examples-hybrid-jobs.rst - - diff --git a/doc/getting-started.rst b/doc/getting-started.rst index 205254740..31493b789 100644 --- a/doc/getting-started.rst +++ b/doc/getting-started.rst @@ -16,7 +16,7 @@ at https://docs.aws.amazon.com/braket/index.html. Getting started using an Amazon Braket notebook ************************************************ -You can use the AWS Console to enable Amazon Braket, +You can use the AWS Console to enable Amazon Braket, then create an Amazon Braket notebook instance and run your first circuit with the Amazon Braket Python SDK: @@ -25,7 +25,7 @@ and run your first circuit with the Amazon Braket Python SDK: 3. `Run your first circuit using the Amazon Braket Python SDK `_. When you use an Amazon Braket notebook, the Amazon Braket SDK and plugins are -preloaded. +preloaded. *********************************** Getting started in your environment @@ -37,4 +37,3 @@ after enabling Amazon Braket and configuring the AWS SDK for Python: 1. `Enable Amazon Braket `_. 2. Configure the AWS SDK for Python (Boto3) using the `Quickstart `_. 3. `Run your first circuit using the Amazon Braket Python SDK `_. - diff --git a/doc/index.rst b/doc/index.rst index 8d996f4cc..54d10b54d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,7 +2,7 @@ Amazon Braket Python SDK ######################## -The Amazon Braket Python SDK is an open source library to design and build quantum circuits, +The Amazon Braket Python SDK is an open source library to design and build quantum circuits, submit them to Amazon Braket devices as quantum tasks, and monitor their execution. This documentation provides information about the Amazon Braket Python SDK library. The project @@ -29,7 +29,7 @@ Explore Amazon Braket examples. :maxdepth: 3 examples.rst - + *************** Python SDK APIs @@ -39,6 +39,5 @@ The Amazon Braket Python SDK APIs: .. toctree:: :maxdepth: 2 - - _apidoc/modules + _apidoc/modules diff --git a/setup.cfg b/setup.cfg index d9dbb5b62..d75c4f034 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ line_length = 100 multi_line_output = 3 include_trailing_comma = true profile = black - + [flake8] ignore = # not pep8, black adds whitespace before ':' @@ -32,7 +32,7 @@ ignore = RST201,RST203,RST301, max_line_length = 100 max-complexity = 10 -exclude = +exclude = __pycache__ .tox .git diff --git a/setup.py b/setup.py index 12bec6f07..6763c4a55 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ from setuptools import find_namespace_packages, setup -with open("README.md", "r") as fh: +with open("README.md") as fh: long_description = fh.read() with open("src/braket/_sdk/_version.py") as f: diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py index 1d47a66b8..24d4fc9aa 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -126,4 +126,4 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: discretized_arrangement.add(new_coordinates, site.site_type) return discretized_arrangement except Exception as e: - raise DiscretizationError(f"Failed to discretize register {e}") + raise DiscretizationError(f"Failed to discretize register {e}") from e diff --git a/src/braket/ahs/discretization_types.py b/src/braket/ahs/discretization_types.py index c7df1fcfc..49efa0d34 100644 --- a/src/braket/ahs/discretization_types.py +++ b/src/braket/ahs/discretization_types.py @@ -18,8 +18,6 @@ class DiscretizationError(Exception): """Raised if the discretization of the numerical values of the AHS program fails.""" - pass - @dataclass class DiscretizationProperties: diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 59732d4ff..00b420210 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -136,7 +136,7 @@ def stitch( stitch_ts.times() = [0, 0.1, 0.3] stitch_ts.values() = [1, 4, 5] """ - if not (self.magnitude.pattern.series == other.magnitude.pattern.series): + if self.magnitude.pattern.series != other.magnitude.pattern.series: raise ValueError("The LocalDetuning pattern for both fields must be equal.") new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 390145aec..70fc3c078 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -354,7 +354,7 @@ def _get_regional_device_session(self, session: AwsSession) -> AwsSession: ValueError(f"'{self._arn}' not found") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e - ) + ) from e def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: current_region = session.region @@ -362,11 +362,10 @@ def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: self._populate_properties(session) return session except ClientError as e: - if e.response["Error"]["Code"] == "ResourceNotFoundException": - if "qpu" not in self._arn: - raise ValueError(f"Simulator '{self._arn}' not found in '{current_region}'") - else: + if e.response["Error"]["Code"] != "ResourceNotFoundException": raise e + if "qpu" not in self._arn: + raise ValueError(f"Simulator '{self._arn}' not found in '{current_region}'") from e # Search remaining regions for QPU for region in frozenset(AwsDevice.REGIONS) - {current_region}: region_session = AwsSession.copy_session(session, region) @@ -623,7 +622,7 @@ def get_devices( f"order_by '{order_by}' must be in {AwsDevice._GET_DEVICES_ORDER_BY_KEYS}" ) types = frozenset(types or AwsDeviceType) - aws_session = aws_session if aws_session else AwsSession() + aws_session = aws_session or AwsSession() device_map = {} session_region = aws_session.boto_session.region_name search_regions = ( @@ -650,13 +649,11 @@ def get_devices( provider_names=provider_names, ) ] - device_map.update( - { - arn: AwsDevice(arn, session_for_region) - for arn in region_device_arns - if arn not in device_map - } - ) + device_map |= { + arn: AwsDevice(arn, session_for_region) + for arn in region_device_arns + if arn not in device_map + } except ClientError as e: error_code = e.response["Error"]["Code"] warnings.warn( @@ -671,29 +668,29 @@ def get_devices( return devices def _update_pulse_properties(self) -> None: - if hasattr(self.properties, "pulse") and isinstance( + if not hasattr(self.properties, "pulse") or not isinstance( self.properties.pulse, PulseDeviceActionProperties ): - if self._ports is None: - self._ports = {} - port_data = self.properties.pulse.ports - for port_id, port in port_data.items(): - self._ports[port_id] = Port( - port_id=port_id, dt=port.dt, properties=json.loads(port.json()) + return + if self._ports is None: + self._ports = {} + port_data = self.properties.pulse.ports + for port_id, port in port_data.items(): + self._ports[port_id] = Port( + port_id=port_id, dt=port.dt, properties=json.loads(port.json()) + ) + if self._frames is None: + self._frames = {} + if frame_data := self.properties.pulse.frames: + for frame_id, frame in frame_data.items(): + self._frames[frame_id] = Frame( + frame_id=frame_id, + port=self._ports[frame.portId], + frequency=frame.frequency, + phase=frame.phase, + is_predefined=True, + properties=json.loads(frame.json()), ) - if self._frames is None: - self._frames = {} - frame_data = self.properties.pulse.frames - if frame_data: - for frame_id, frame in frame_data.items(): - self._frames[frame_id] = Frame( - frame_id=frame_id, - port=self._ports[frame.portId], - frequency=frame.frequency, - phase=frame.phase, - is_predefined=True, - properties=json.loads(frame.json()), - ) @staticmethod def get_device_region(device_arn: str) -> str: @@ -710,11 +707,11 @@ def get_device_region(device_arn: str) -> str: """ try: return device_arn.split(":")[3] - except IndexError: + except IndexError as e: raise ValueError( f"Device ARN is not a valid format: {device_arn}. For valid Braket ARNs, " "see 'https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html'" - ) + ) from e def queue_depth(self) -> QueueDepthInfo: """Task queue depth refers to the total number of quantum tasks currently waiting @@ -788,10 +785,10 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: json.loads(f.read().decode("utf-8")) ) return GateCalibrations(json_calibration_data) - except urllib.error.URLError: + except urllib.error.URLError as e: raise urllib.error.URLError( f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" - ) + ) from e else: return None diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 3c6dbe66e..37eb67603 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -412,7 +412,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: has_streams, color_wrap, [previous_state, current_state], - self.queue_position().queue_position if not self._quiet else None, + None if self._quiet else self.queue_position().queue_position, ) previous_state = current_state @@ -575,17 +575,16 @@ def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) s3_uri=output_bucket_uri, filename=AwsQuantumJob.RESULTS_TAR_FILENAME ) except ClientError as e: - if e.response["Error"]["Code"] == "404": - exception_response = { - "Error": { - "Code": "404", - "Message": f"Error retrieving results, " - f"could not find results at '{output_s3_path}'", - } - } - raise ClientError(exception_response, "HeadObject") from e - else: + if e.response["Error"]["Code"] != "404": raise e + exception_response = { + "Error": { + "Code": "404", + "Message": f"Error retrieving results, " + f"could not find results at '{output_s3_path}'", + } + } + raise ClientError(exception_response, "HeadObject") from e @staticmethod def _extract_tar_file(extract_path: str) -> None: @@ -596,9 +595,7 @@ def __repr__(self) -> str: return f"AwsQuantumJob('arn':'{self.arn}')" def __eq__(self, other: AwsQuantumJob) -> bool: - if isinstance(other, AwsQuantumJob): - return self.arn == other.arn - return False + return self.arn == other.arn if isinstance(other, AwsQuantumJob) else False def __hash__(self) -> int: return hash(self.arn) @@ -632,7 +629,7 @@ def _initialize_regional_device_session( ValueError(f"'{device}' not found.") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e - ) + ) from e @staticmethod def _initialize_non_regional_device_session( @@ -643,12 +640,11 @@ def _initialize_non_regional_device_session( aws_session.get_device(device) return aws_session except ClientError as e: - if e.response["Error"]["Code"] == "ResourceNotFoundException": - if "qpu" not in device: - raise ValueError(f"Simulator '{device}' not found in '{original_region}'") - else: + if e.response["Error"]["Code"] != "ResourceNotFoundException": raise e + if "qpu" not in device: + raise ValueError(f"Simulator '{device}' not found in '{original_region}'") from e for region in frozenset(AwsDevice.REGIONS) - {original_region}: device_session = aws_session.copy_session(region=region) try: diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index c386469b0..17ae29252 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -205,8 +205,7 @@ def create( if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(inputs.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(inputs.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" ) @@ -294,11 +293,9 @@ def id(self) -> str: def _cancel_future(self) -> None: """Cancel the future if it exists. Else, create a cancelled future.""" - if hasattr(self, "_future"): - self._future.cancel() - else: + if not hasattr(self, "_future"): self._future = asyncio.Future() - self._future.cancel() + self._future.cancel() def cancel(self) -> None: """Cancel the quantum task. This cancels the future and the quantum task in Amazon @@ -509,10 +506,10 @@ async def _wait_for_completion( return None def _has_reservation_arn_from_metadata(self, current_metadata: dict[str, Any]) -> bool: - for association in current_metadata.get("associations", []): - if association.get("type") == "RESERVATION_TIME_WINDOW_ARN": - return True - return False + return any( + association.get("type") == "RESERVATION_TIME_WINDOW_ARN" + for association in current_metadata.get("associations", []) + ) def _download_result( self, @@ -544,9 +541,7 @@ def __repr__(self) -> str: return f"AwsQuantumTask('id/taskArn':'{self.id}')" def __eq__(self, other: AwsQuantumTask) -> bool: - if isinstance(other, AwsQuantumTask): - return self.id == other.id - return False + return self.id == other.id if isinstance(other, AwsQuantumTask) else False def __hash__(self) -> int: return hash(self.id) @@ -583,10 +578,10 @@ def _( ) -> AwsQuantumTask: openqasm_program = OpenQASMProgram( source=pulse_sequence.to_ir(), - inputs=inputs if inputs else {}, + inputs=inputs or {}, ) - create_task_kwargs.update({"action": openqasm_program.json()}) + create_task_kwargs["action"] = openqasm_program.json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -611,7 +606,7 @@ def _( source=openqasm_program.source, inputs=inputs_copy, ) - create_task_kwargs.update({"action": openqasm_program.json()}) + create_task_kwargs["action"] = openqasm_program.json() if device_parameters: final_device_parameters = ( _circuit_device_params_from_dict( @@ -622,9 +617,7 @@ def _( if isinstance(device_parameters, dict) else device_parameters ) - create_task_kwargs.update( - {"deviceParameters": final_device_parameters.json(exclude_none=True)} - ) + create_task_kwargs["deviceParameters"] = final_device_parameters.json(exclude_none=True) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -643,7 +636,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": blackbird_program.json()}) + create_task_kwargs["action"] = blackbird_program.json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -701,12 +694,10 @@ def _( inputs=inputs_copy, ) - create_task_kwargs.update( - { - "action": openqasm_program.json(), - "deviceParameters": final_device_parameters.json(exclude_none=True), - } - ) + create_task_kwargs |= { + "action": openqasm_program.json(), + "deviceParameters": final_device_parameters.json(exclude_none=True), + } task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -730,12 +721,10 @@ def _( **kwargs, ) -> AwsQuantumTask: device_params = _create_annealing_device_params(device_parameters, device_arn) - create_task_kwargs.update( - { - "action": problem.to_ir().json(), - "deviceParameters": device_params.json(exclude_none=True), - } - ) + create_task_kwargs |= { + "action": problem.to_ir().json(), + "deviceParameters": device_params.json(exclude_none=True), + } task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -754,7 +743,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": analog_hamiltonian_simulation.to_ir().json()}) + create_task_kwargs["action"] = analog_hamiltonian_simulation.to_ir().json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 964fabe7e..300963a6f 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -209,8 +209,7 @@ def _tasks_inputs_gatedefs( for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(input_map.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" @@ -323,9 +322,7 @@ def _create_task( # If the quantum task hits a terminal state before all quantum tasks have been created, # it can be returned immediately - while remaining: - if task.state() in AwsQuantumTask.TERMINAL_STATES: - break + while remaining and task.state() not in AwsQuantumTask.TERMINAL_STATES: time.sleep(poll_interval_seconds) return task @@ -363,7 +360,7 @@ def results( retries = 0 while self._unsuccessful and retries < max_retries: self.retry_unsuccessful_tasks() - retries = retries + 1 + retries += 1 if fail_unsuccessful and self._unsuccessful: raise RuntimeError( diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 6a1bd1ce8..16a021e7d 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -238,7 +238,7 @@ def create_quantum_task(self, **boto3_kwargs) -> str: # Add job token to request, if available. job_token = os.getenv("AMZN_BRAKET_JOB_TOKEN") if job_token: - boto3_kwargs.update({"jobToken": job_token}) + boto3_kwargs["jobToken"] = job_token response = self.braket_client.create_quantum_task(**boto3_kwargs) broadcast_event( _TaskCreationEvent( @@ -402,7 +402,7 @@ def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: relative_prefix = str(Path(local_prefix).relative_to(base_dir)) else: base_dir = Path() - relative_prefix = str(local_prefix) + relative_prefix = local_prefix for file in itertools.chain( # files that match the prefix base_dir.glob(f"{relative_prefix}*"), @@ -579,7 +579,12 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) error_code = e.response["Error"]["Code"] message = e.response["Error"]["Message"] - if error_code == "BucketAlreadyOwnedByYou": + if ( + error_code == "BucketAlreadyOwnedByYou" + or error_code != "BucketAlreadyExists" + and error_code == "OperationAborted" + and "conflicting conditional operation" in message + ): pass elif error_code == "BucketAlreadyExists": raise ValueError( @@ -587,12 +592,6 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) f"for another account. Please supply alternative " f"bucket name via AwsSession constructor `AwsSession()`." ) from None - elif ( - error_code == "OperationAborted" and "conflicting conditional operation" in message - ): - # If this bucket is already being concurrently created, we don't need to create - # it again. - pass else: raise @@ -691,8 +690,8 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]: raise AssertionError bucket, key = s3_uri_match.groups() return bucket, key - except (AssertionError, ValueError): - raise ValueError(f"Not a valid S3 uri: {s3_uri}") + except (AssertionError, ValueError) as e: + raise ValueError(f"Not a valid S3 uri: {s3_uri}") from e @staticmethod def construct_s3_uri(bucket: str, *dirs: str) -> str: @@ -740,10 +739,10 @@ def describe_log_streams( } if limit: - log_stream_args.update({"limit": limit}) + log_stream_args["limit"] = limit if next_token: - log_stream_args.update({"nextToken": next_token}) + log_stream_args["nextToken"] = next_token return self.logs_client.describe_log_streams(**log_stream_args) @@ -777,7 +776,7 @@ def get_log_events( } if next_token: - log_events_args.update({"nextToken": next_token}) + log_events_args["nextToken"] = next_token return self.logs_client.get_log_events(**log_events_args) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index bf0ee07d8..94d53248c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -164,11 +164,9 @@ def depth(self) -> int: def global_phase(self) -> float: """float: Get the global phase of the circuit.""" return sum( - [ - instr.operator.angle - for moment, instr in self._moments.items() - if moment.moment_type == MomentType.GLOBAL_PHASE - ] + instr.operator.angle + for moment, instr in self._moments.items() + if moment.moment_type == MomentType.GLOBAL_PHASE ) @property @@ -196,8 +194,7 @@ def basis_rotation_instructions(self) -> list[Instruction]: # Note that basis_rotation_instructions can change each time a new instruction # is added to the circuit because `self._moments.qubits` would change basis_rotation_instructions = [] - all_qubit_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) - if all_qubit_observable: + if all_qubit_observable := self._qubit_observable_mapping.get(Circuit._ALL_QUBITS): for target in self.qubits: basis_rotation_instructions += Circuit._observable_to_instruction( all_qubit_observable, target @@ -880,7 +877,7 @@ def apply_gate_noise( # check target_qubits target_qubits = check_noise_target_qubits(self, target_qubits) - if not all(qubit in self.qubits for qubit in target_qubits): + if any(qubit not in self.qubits for qubit in target_qubits): raise IndexError("target_qubits must be within the range of the current circuit.") # Check if there is a measure instruction on the circuit @@ -1007,9 +1004,7 @@ def _validate_parameters(self, parameter_values: dict[str, Number]) -> None: ValueError: If there are no parameters that match the key for the arg param_values. """ - parameter_strings = set() - for parameter in self.parameters: - parameter_strings.add(str(parameter)) + parameter_strings = {str(parameter) for parameter in self.parameters} for param in parameter_values: if param not in parameter_strings: raise ValueError(f"No parameter in the circuit named: {param}") @@ -1116,7 +1111,7 @@ def apply_readout_noise( target_qubits = [target_qubits] if not all(isinstance(q, int) for q in target_qubits): raise TypeError("target_qubits must be integer(s)") - if not all(q >= 0 for q in target_qubits): + if any(q < 0 for q in target_qubits): raise ValueError("target_qubits must contain only non-negative integers.") target_qubits = QubitSet(target_qubits) @@ -1349,8 +1344,7 @@ def _create_openqasm_header( ) -> list[str]: ir_instructions = ["OPENQASM 3.0;"] frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) - for parameter in self.parameters: - ir_instructions.append(f"input float {parameter};") + ir_instructions.extend(f"input float {parameter};" for parameter in self.parameters) if not self.result_types: bit_count = ( len(self._measure_targets) @@ -1378,7 +1372,7 @@ def _validate_gate_calibrations_uniqueness( frames: dict[str, Frame], waveforms: dict[str, Waveform], ) -> None: - for _key, calibration in gate_definitions.items(): + for calibration in gate_definitions.values(): for frame in calibration._frames.values(): _validate_uniqueness(frames, frame) frames[frame.id] = frame @@ -1466,7 +1460,7 @@ def _get_frames_waveforms_from_instrs( fixed_argument_calibrations = self._add_fixed_argument_calibrations( gate_definitions, instruction ) - gate_definitions.update(fixed_argument_calibrations) + gate_definitions |= fixed_argument_calibrations return frames, waveforms def _add_fixed_argument_calibrations( @@ -1509,7 +1503,7 @@ def _add_fixed_argument_calibrations( instruction.operator.parameters ) == len(gate.parameters): free_parameter_number = sum( - [isinstance(p, FreeParameterExpression) for p in gate.parameters] + isinstance(p, FreeParameterExpression) for p in gate.parameters ) if free_parameter_number == 0: continue @@ -1563,10 +1557,10 @@ def to_unitary(self) -> np.ndarray: [ 0.70710678+0.j, 0. +0.j, -0.70710678+0.j, 0. +0.j]]) """ - qubits = self.qubits - if not qubits: + if qubits := self.qubits: + return calculate_unitary_big_endian(self.instructions, qubits) + else: return np.zeros(0, dtype=complex) - return calculate_unitary_big_endian(self.instructions, qubits) @property def qubits_frozen(self) -> bool: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index bc4c2f9e9..ee5ea684b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -3702,9 +3702,7 @@ def _to_openqasm( return f"#pragma braket unitary({formatted_matrix}) {', '.join(qubits)}" def __eq__(self, other: Unitary): - if isinstance(other, Unitary): - return self.matrix_equivalence(other) - return False + return self.matrix_equivalence(other) if isinstance(other, Unitary) else False def __hash__(self): return hash((self.name, str(self._matrix), self.qubit_count)) @@ -3859,11 +3857,10 @@ def format_complex(number: complex) -> str: str: The formatted string. """ if number.real: - if number.imag: - imag_sign = "+" if number.imag > 0 else "-" - return f"{number.real} {imag_sign} {abs(number.imag)}im" - else: + if not number.imag: return f"{number.real}" + imag_sign = "+" if number.imag > 0 else "-" + return f"{number.real} {imag_sign} {abs(number.imag)}im" elif number.imag: return f"{number.imag}im" else: diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 66ea41913..b2dee4151 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -238,7 +238,7 @@ def add_noise( time = 0 while MomentsKey(time, qubit_range, input_type, noise_index) in self._moments: - noise_index = noise_index + 1 + noise_index += 1 self._moments[MomentsKey(time, qubit_range, input_type, noise_index)] = instruction self._qubits.update(qubit_range) @@ -341,9 +341,7 @@ def __eq__(self, other: Moments): def __ne__(self, other: Moments): result = self.__eq__(other) - if result is not NotImplemented: - return not result - return NotImplemented + return not result if result is not NotImplemented else NotImplemented def __repr__(self): return self._moments.__repr__() diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 0cad544e2..e5d4fdf8a 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -137,9 +137,7 @@ def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: raise NotImplementedError("to_matrix has not been implemented yet.") def __eq__(self, other: Noise): - if isinstance(other, Noise): - return self.name == other.name - return False + return self.name == other.name if isinstance(other, Noise) else False def __repr__(self): return f"{self.name}('qubit_count': {self.qubit_count})" @@ -468,9 +466,10 @@ def to_dict(self) -> dict: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ - probabilities = {} - for pauli_string, prob in self._probabilities.items(): - probabilities[pauli_string] = _parameter_to_dict(prob) + probabilities = { + pauli_string: _parameter_to_dict(prob) + for pauli_string, prob in self._probabilities.items() + } return { "__class__": self.__class__.__name__, "probabilities": probabilities, @@ -537,9 +536,8 @@ def _get_param_float(param: Union[FreeParameterExpression, float], param_name: s """ if isinstance(param, FreeParameterExpression): return 0 - else: - _validate_param_value(param, param_name) - return float(param) + _validate_param_value(param, param_name) + return float(param) @property def probX(self) -> Union[FreeParameterExpression, float]: diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 89f30cac8..a73b7f338 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -36,7 +36,7 @@ def no_noise_applied_warning(noise_applied: bool) -> None: Args: noise_applied (bool): True if the noise has been applied. """ - if noise_applied is False: + if not noise_applied: warnings.warn( "Noise is not applied to any gate, as there is no eligible gate in the circuit" " with the input criteria or there is no multi-qubit gate to apply" @@ -122,7 +122,7 @@ def check_noise_target_qubits( target_qubits = wrap_with_list(target_qubits) if not all(isinstance(q, int) for q in target_qubits): raise TypeError("target_qubits must be integer(s)") - if not all(q >= 0 for q in target_qubits): + if any(q < 0 for q in target_qubits): raise ValueError("target_qubits must contain only non-negative integers.") target_qubits = QubitSet(target_qubits) diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py index 1db40aa5f..4dceeb4fb 100644 --- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py +++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py @@ -53,6 +53,4 @@ def _check_target_in_qubits( if qubits is None: return True target = [int(item) for item in target] - if len(target) == 1: - return target[0] in qubits - return tuple(target) in qubits + return target[0] in qubits if len(target) == 1 else tuple(target) in qubits diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py index 63625491f..889211342 100644 --- a/src/braket/circuits/noise_model/criteria.py +++ b/src/braket/circuits/noise_model/criteria.py @@ -73,10 +73,10 @@ def __eq__(self, other: Criteria): return NotImplemented if self.applicable_key_types() != other.applicable_key_types(): return False - for key_type in self.applicable_key_types(): - if self.get_keys(key_type) != other.get_keys(key_type): - return False - return True + return all( + self.get_keys(key_type) == other.get_keys(key_type) + for key_type in self.applicable_key_types() + ) @abstractmethod def to_dict(self) -> dict: diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py index bc86e53bf..456867ce2 100644 --- a/src/braket/circuits/noise_model/criteria_input_parsing.py +++ b/src/braket/circuits/noise_model/criteria_input_parsing.py @@ -84,6 +84,4 @@ def parse_qubit_input( if qubit_count == 1: return {item[0] for item in qubits} return {tuple(item) for item in qubits} - if qubit_count > 1: - return {tuple(qubits)} - return set(qubits) + return {tuple(qubits)} if qubit_count > 1 else set(qubits) diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 4ea566131..e8a603075 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -343,10 +343,9 @@ def _items_to_string( list[str]: A list of string representations of the passed instructions. """ results = [] - if len(instructions) > 0: + if instructions: results.append(instructions_title) - for item in instructions: - results.append(f" {item}") + results.extend(f" {item}" for item in instructions) return results @classmethod diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py index 1a2126502..5cb510f2e 100644 --- a/src/braket/circuits/noise_model/observable_criteria.py +++ b/src/braket/circuits/noise_model/observable_criteria.py @@ -132,9 +132,7 @@ def result_type_matches(self, result_type: ResultType) -> bool: if self._qubits is None: return True target = list(result_type.target) - if not target: - return True - return target[0] in self._qubits + return target[0] in self._qubits if target else True @classmethod def from_dict(cls, criteria: dict) -> Criteria: diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index e1790fd21..26594ca60 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -59,9 +59,7 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: All other keys will return an empty set. """ if key_type == CriteriaKey.QUBIT: - if self._qubits is None: - return CriteriaKeyResult.ALL - return set(self._qubits) + return CriteriaKeyResult.ALL if self._qubits is None else set(self._qubits) return set() def to_dict(self) -> dict: diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 904f7ac74..a8829f1a4 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -953,9 +953,10 @@ def from_dict(cls, noise: dict) -> Noise: Returns: Noise: A Noise object that represents the passed in dictionary. """ - probabilities = {} - for pauli_string, prob in noise["probabilities"].items(): - probabilities[pauli_string] = _parameter_from_dict(prob) + probabilities = { + pauli_string: _parameter_from_dict(prob) + for pauli_string, prob in noise["probabilities"].items() + } return TwoQubitPauliChannel(probabilities=probabilities) @@ -1327,7 +1328,7 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): """ for matrix in matrices: verify_quantum_operator_matrix_dimensions(matrix) - if not int(np.log2(matrix.shape[0])) == int(np.log2(matrices[0].shape[0])): + if int(np.log2(matrix.shape[0])) != int(np.log2(matrices[0].shape[0])): raise ValueError(f"all matrices in {matrices} must have the same shape") self._matrices = [np.array(matrix, dtype=complex) for matrix in matrices] self._display_name = display_name @@ -1449,11 +1450,10 @@ def _ascii_representation( Returns: str: The ascii representation of the noise. """ - param_list = [] - for param in parameters: - param_list.append( - str(param) if isinstance(param, FreeParameterExpression) else f"{param:.2g}" - ) + param_list = [ + (str(param) if isinstance(param, FreeParameterExpression) else f"{param:.2g}") + for param in parameters + ] param_str = ",".join(param_list) return f"{noise}({param_str})" diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 2d5ccdb68..ae4f36a09 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -67,7 +67,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Ry(-math.pi / 4)]) # noqa: C409 + return (Gate.Ry(-math.pi / 4),) Observable.register_observable(H) @@ -155,7 +155,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.H()]) # noqa: C409 + return (Gate.H(),) Observable.register_observable(X) @@ -193,7 +193,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Z(), Gate.S(), Gate.H()]) # noqa: C409 + return Gate.Z(), Gate.S(), Gate.H() Observable.register_observable(Y) @@ -266,15 +266,14 @@ def __init__(self, observables: list[Observable]): flattened_observables = [] for obs in observables: if isinstance(obs, TensorProduct): - for nested_obs in obs.factors: - flattened_observables.append(nested_obs) + flattened_observables.extend(iter(obs.factors)) # make sure you don't lose coefficient of tensor product flattened_observables[-1] *= obs.coefficient elif isinstance(obs, Sum): raise TypeError("Sum observables not allowed in TensorProduct") else: flattened_observables.append(obs) - qubit_count = sum([obs.qubit_count for obs in flattened_observables]) + qubit_count = sum(obs.qubit_count for obs in flattened_observables) # aggregate all coefficients for the product, since aX @ bY == ab * X @ Y coefficient = np.prod([obs.coefficient for obs in flattened_observables]) unscaled_factors = tuple(obs._unscaled() for obs in flattened_observables) @@ -447,8 +446,7 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni flattened_observables = [] for obs in observables: if isinstance(obs, Sum): - for nested_obs in obs.summands: - flattened_observables.append(nested_obs) + flattened_observables.extend(iter(obs.summands)) else: flattened_observables.append(obs) @@ -661,9 +659,8 @@ def observable_from_ir(ir_observable: list[Union[str, list[list[list[float]]]]]) """ if len(ir_observable) == 1: return _observable_from_ir_list_item(ir_observable[0]) - else: - observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) - return observable + observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) + return observable def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]]]) -> Observable: @@ -684,4 +681,4 @@ def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]] ) return Hermitian(matrix) except Exception as e: - raise ValueError(f"Invalid observable specified: {observable} error: {e}") + raise ValueError(f"Invalid observable specified: {observable} error: {e}") from e diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 068c4171d..b706e1822 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -52,12 +52,12 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): fixed_qubit_count = self.fixed_qubit_count() if fixed_qubit_count is NotImplemented: self._qubit_count = qubit_count + elif qubit_count and qubit_count != fixed_qubit_count: + raise ValueError( + f"Provided qubit count {qubit_count}" + "does not equal fixed qubit count {fixed_qubit_count}" + ) else: - if qubit_count and qubit_count != fixed_qubit_count: - raise ValueError( - f"Provided qubit count {qubit_count}" - "does not equal fixed qubit count {fixed_qubit_count}" - ) self._qubit_count = fixed_qubit_count if not isinstance(self._qubit_count, int): diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 15cb8d1fd..10c22808e 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -99,7 +99,7 @@ def is_cptp(matrices: Iterable[np.ndarray]) -> bool: Returns: bool: If the matrices define a CPTP map. """ - E = sum([np.dot(matrix.T.conjugate(), matrix) for matrix in matrices]) + E = sum(np.dot(matrix.T.conjugate(), matrix) for matrix in matrices) return np.allclose(E, np.eye(*E.shape)) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 0b73c5e7b..325fa8f46 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -68,9 +68,7 @@ def state_vector() -> ResultType: return ResultType.StateVector() def __eq__(self, other: StateVector) -> bool: - if isinstance(other, StateVector): - return True - return False + return isinstance(other, StateVector) def __copy__(self) -> StateVector: return type(self)() @@ -334,9 +332,7 @@ def amplitude(state: list[str]) -> ResultType: return ResultType.Amplitude(state=state) def __eq__(self, other: Amplitude): - if isinstance(other, Amplitude): - return self.state == other.state - return False + return self.state == other.state if isinstance(other, Amplitude) else False def __repr__(self): return f"Amplitude(state={self.state})" @@ -424,9 +420,7 @@ def probability(target: QubitSetInput | None = None) -> ResultType: return ResultType.Probability(target=target) def __eq__(self, other: Probability) -> bool: - if isinstance(other, Probability): - return self.target == other.target - return False + return self.target == other.target if isinstance(other, Probability) else False def __repr__(self) -> str: return f"Probability(target={self.target})" diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index a633d318c..4a7c9565c 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -124,9 +124,7 @@ def _create_diagram_column( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = { - qubit: state for qubit, state in zip(control_qubits, control_state) - } + map_control_qubit_states = dict(zip(control_qubits, control_state)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -188,8 +186,12 @@ def _draw_symbol( str: a string representing the symbol. """ connection_char = cls._vertical_delimiter() if connection in ["above"] else " " - output = "{0:{width}}\n".format(connection_char, width=symbols_width + 1) - output += "{0:{fill}{align}{width}}\n".format( - symbol, fill=cls._qubit_line_character(), align="<", width=symbols_width + 1 + output = "{0:{width}}\n".format( + connection_char, width=symbols_width + 1 + ) + "{0:{fill}{align}{width}}\n".format( + symbol, + fill=cls._qubit_line_character(), + align="<", + width=symbols_width + 1, ) return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index e1dda5a3b..ad30b34a7 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -244,7 +244,7 @@ def _create_output( Returns: str: a string representing a diagram column. """ - symbols_width = max([len(symbol) for symbol in symbols.values()]) + cls._box_pad() + symbols_width = max(len(symbol) for symbol in symbols.values()) + cls._box_pad() output = "" if global_phase is not None: diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 83763a9ae..f261b00b6 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -103,14 +103,15 @@ def _compute_moment_global_phase( Returns: float | None: The updated integrated phase. """ - moment_phase = 0 - for item in items: + moment_phase = sum( + item.operator.angle + for item in items if ( isinstance(item, Instruction) and isinstance(item.operator, Gate) and item.operator.name == "GPhase" - ): - moment_phase += item.operator.angle + ) + ) return global_phase + moment_phase if global_phase is not None else None diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 0739724e9..85567de28 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -165,9 +165,7 @@ def _build_parameters( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = { - qubit: state for qubit, state in zip(control_qubits, control_state) - } + map_control_qubit_states = dict(zip(control_qubits, control_state)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -213,7 +211,7 @@ def _draw_symbol( """ top = "" bottom = "" - if symbol in ["C", "N", "SWAP"]: + if symbol in {"C", "N", "SWAP"}: if connection in ["above", "both"]: top = _fill_symbol(cls._vertical_delimiter(), " ") if connection in ["below", "both"]: @@ -227,10 +225,7 @@ def _draw_symbol( elif symbol == "┼": top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") symbol = _fill_symbol(f"{symbol}", cls._qubit_line_character()) - elif symbol == cls._qubit_line_character(): - # We do not box when no gate is applied. - pass - else: + elif symbol != cls._qubit_line_character(): top, symbol, bottom = cls._build_box(symbol, connection) output = f"{_fill_symbol(top, ' ', symbols_width)} \n" diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 3f2a28e41..dbc7b6b35 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -117,12 +117,12 @@ def status(self) -> str: return self._status def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: - supported_noises = set( + supported_noises = { SUPPORTED_NOISE_PRAGMA_TO_NOISE[pragma].__name__ for pragma in self.properties.action[DeviceActionType.OPENQASM].supportedPragmas if pragma in SUPPORTED_NOISE_PRAGMA_TO_NOISE - ) - noise_operators = set(noise_instr.noise.name for noise_instr in noise_model._instructions) + } + noise_operators = {noise_instr.noise.name for noise_instr in noise_model._instructions} if not noise_operators <= supported_noises: raise ValueError( f"{self.name} does not support noise simulation or the noise model includes noise " diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 69fcfdaff..1dec56d37 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -168,9 +168,8 @@ def run_batch( # noqa: C901 single_input = isinstance(inputs, dict) - if not single_task and not single_input: - if len(task_specifications) != len(inputs): - raise ValueError("Multiple inputs and task specifications must be equal in number.") + if not single_task and not single_input and len(task_specifications) != len(inputs): + raise ValueError("Multiple inputs and task specifications must be equal in number.") if single_task: task_specifications = repeat(task_specifications) @@ -187,8 +186,7 @@ def run_batch( # noqa: C901 for task_specification, input_map in tasks_and_inputs: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(input_map.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" @@ -237,13 +235,12 @@ def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulat @_get_simulator.register def _(self, backend_name: str): - if backend_name in _simulator_devices: - device_class = _simulator_devices[backend_name].load() - return device_class() - else: + if backend_name not in _simulator_devices: raise ValueError( f"Only the following devices are available {_simulator_devices.keys()}" ) + device_class = _simulator_devices[backend_name].load() + return device_class() @_get_simulator.register def _(self, backend_impl: BraketSimulator): diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index c443d1b44..d850ee85c 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -23,8 +23,6 @@ def running_in_jupyter() -> bool: bool: True if running in Jupyter, else False. """ in_ipython = False - in_ipython_kernel = False - # if IPython hasn't been imported, there's nothing to check if "IPython" in sys.modules: get_ipython = sys.modules["IPython"].__dict__["get_ipython"] @@ -32,7 +30,4 @@ def running_in_jupyter() -> bool: ip = get_ipython() in_ipython = ip is not None - if in_ipython: - in_ipython_kernel = getattr(ip, "kernel", None) is not None - - return in_ipython_kernel + return getattr(ip, "kernel", None) is not None if in_ipython else False diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py index ad42006de..6d7d18364 100644 --- a/src/braket/jobs/environment_variables.py +++ b/src/braket/jobs/environment_variables.py @@ -44,9 +44,7 @@ def get_input_data_dir(channel: str = "input") -> str: str: The input directory, defaulting to current working directory. """ input_dir = os.getenv("AMZN_BRAKET_INPUT_DIR", ".") - if input_dir != ".": - return f"{input_dir}/{channel}" - return input_dir + return f"{input_dir}/{channel}" if input_dir != "." else input_dir def get_results_dir() -> str: diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index 3cd622e37..77f5f43d0 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -247,7 +247,7 @@ def _validate_python_version(image_uri: str | None, aws_session: AwsSession | No image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region) tag = aws_session.get_full_image_tag(image_uri) major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() - if not (sys.version_info.major, sys.version_info.minor) == ( + if (sys.version_info.major, sys.version_info.minor) != ( int(major_version), int(minor_version), ): @@ -369,9 +369,7 @@ def _process_input_data(input_data: dict) -> list[str]: input_data = {"input": input_data} def matches(prefix: str) -> list[str]: - return [ - str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(str(prefix)) - ] + return [str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(prefix)] def is_prefix(path: str) -> bool: return len(matches(path)) > 1 or not Path(path).exists() @@ -388,7 +386,7 @@ def is_prefix(path: str) -> bool: f"the working directory. Use `get_input_data_dir({channel_arg})` to read " f"input data from S3 source inside the job container." ) - elif is_prefix(data): + elif is_prefix(str(data)): prefix_channels.add(channel) elif Path(data).is_dir(): directory_channels.add(channel) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index af6806157..4dd15607a 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -211,8 +211,10 @@ def run_log(self) -> str: try: with open(os.path.join(self.name, "log.txt")) as log_file: self._run_log = log_file.read() - except FileNotFoundError: - raise ValueError(f"Unable to find logs in the local job directory {self.name}.") + except FileNotFoundError as e: + raise ValueError( + f"Unable to find logs in the local job directory {self.name}." + ) from e return self._run_log def state(self, use_cached_value: bool = False) -> str: @@ -241,7 +243,6 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: Returns: dict[str, Any]: None """ - pass def cancel(self) -> str: """When running the hybrid job in local mode, the cancelling a running is not possible. @@ -249,7 +250,6 @@ def cancel(self) -> str: Returns: str: None """ - pass def download_result( self, @@ -268,7 +268,6 @@ def download_result( poll_interval_seconds (float): The polling interval, in seconds, for `result()`. Default: 5 seconds. """ - pass def result( self, @@ -296,8 +295,10 @@ def result( persisted_data.dataDictionary, persisted_data.dataFormat ) return deserialized_data - except FileNotFoundError: - raise ValueError(f"Unable to find results in the local job directory {self.name}.") + except FileNotFoundError as e: + raise ValueError( + f"Unable to find results in the local job directory {self.name}." + ) from e def metrics( self, diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 04aeaff1a..6d9d08f4f 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -138,8 +138,8 @@ def _pull_image(self, image_uri: str) -> None: "Please pull down the container, or specify a valid ECR URL, " "before proceeding." ) - ecr_url = ecr_pattern_match.group(1) - account_id = ecr_pattern_match.group(2) + ecr_url = ecr_pattern_match[1] + account_id = ecr_pattern_match[2] self._login_to_ecr(account_id, ecr_url) self._logger.warning("Pulling docker container image. This may take a while.") subprocess.run(["docker", "pull", image_uri]) diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 57c1f3653..65cef387c 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -41,7 +41,7 @@ def setup_container( logger = getLogger(__name__) _create_expected_paths(container, **creation_kwargs) run_environment_variables = {} - run_environment_variables.update(_get_env_credentials(aws_session, logger)) + run_environment_variables |= _get_env_credentials(aws_session, logger) run_environment_variables.update( _get_env_script_mode_config(creation_kwargs["algorithmSpecification"]["scriptModeConfig"]) ) @@ -222,8 +222,10 @@ def _download_input_data( found_item = False try: Path(download_dir, channel_name).mkdir() - except FileExistsError: - raise ValueError(f"Duplicate channel names not allowed for input data: {channel_name}") + except FileExistsError as e: + raise ValueError( + f"Duplicate channel names not allowed for input data: {channel_name}" + ) from e for s3_key in s3_keys: relative_key = Path(s3_key).relative_to(top_level) download_path = Path(download_dir, channel_name, relative_key) diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 5e2f12f82..9aa7dfaca 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -210,9 +210,9 @@ def flush_log_streams( # noqa C901 if s["logStreamName"] not in stream_names ] stream_names.extend(new_streams) - positions.update( - [(s, Position(timestamp=0, skip=0)) for s in stream_names if s not in positions] - ) + positions |= [ + (s, Position(timestamp=0, skip=0)) for s in stream_names if s not in positions + ] except ClientError as e: # On the very first training job run on an account, there's no # log group until the container starts logging, so ignore any @@ -221,7 +221,7 @@ def flush_log_streams( # noqa C901 if err.get("Code") != "ResourceNotFoundException": raise - if len(stream_names) > 0: + if stream_names: if not has_streams: print() has_streams = True diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index b2d12fe36..8f5d3dcd5 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -105,8 +105,7 @@ def _parse_log_line(self, result_entry: list[dict[str, Any]], parser: LogMetrics and other metadata that we (currently) do not use. parser (LogMetricsParser) : The CWL metrics parser. """ - message = self._get_element_from_log_line("@message", result_entry) - if message: + if message := self._get_element_from_log_line("@message", result_entry): timestamp = self._get_element_from_log_line("@timestamp", result_entry) parser.parse_log_message(timestamp, message) diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index 8a5a5333d..e8da4ff89 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -53,9 +53,7 @@ def _is_metrics_message(message: str) -> bool: Returns: bool: True if the given message is designated as containing Metrics; False otherwise. """ - if message: - return "Metrics -" in message - return False + return "Metrics -" in message if message else False def _parse_metrics_from_log_stream( self, @@ -105,21 +103,19 @@ def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> list[s """ kwargs = { "logGroupName": self.LOG_GROUP_NAME, - "logStreamNamePrefix": job_name + "/algo-", + "logStreamNamePrefix": f"{job_name}/algo-", } log_streams = [] while time.time() < timeout_time: response = self._logs_client.describe_log_streams(**kwargs) - streams = response.get("logStreams") - if streams: + if streams := response.get("logStreams"): for stream in streams: - name = stream.get("logStreamName") - if name: + if name := stream.get("logStreamName"): log_streams.append(name) - next_token = response.get("nextToken") - if not next_token: + if next_token := response.get("nextToken"): + kwargs["nextToken"] = next_token + else: return log_streams - kwargs["nextToken"] = next_token self._logger.warning("Timed out waiting for all metrics. Data may be incomplete.") return log_streams diff --git a/src/braket/jobs/metrics_data/exceptions.py b/src/braket/jobs/metrics_data/exceptions.py index 677a3a447..41cbf0491 100644 --- a/src/braket/jobs/metrics_data/exceptions.py +++ b/src/braket/jobs/metrics_data/exceptions.py @@ -14,5 +14,3 @@ class MetricsRetrievalError(Exception): """Raised when retrieving metrics fails.""" - - pass diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index 82142a589..1ff5b4d49 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -101,8 +101,7 @@ def parse_log_message(self, timestamp: str, message: str) -> None: return if timestamp and self.TIMESTAMP not in parsed_metrics: parsed_metrics[self.TIMESTAMP] = timestamp - node_match = self.NODE_TAG.match(message) - if node_match: + if node_match := self.NODE_TAG.match(message): parsed_metrics[self.NODE_ID] = node_match.group(1) self.all_metrics.append(parsed_metrics) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 98905dc56..3c4a01b5c 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -224,7 +224,7 @@ def prepare_quantum_job( "sagemaker_distributed_dataparallel_enabled": "true", "sagemaker_instance_type": instance_config.instanceType, } - hyperparameters.update(distributed_hyperparams) + hyperparameters |= distributed_hyperparams create_job_kwargs = { "jobName": job_name, @@ -241,16 +241,12 @@ def prepare_quantum_job( } if reservation_arn: - create_job_kwargs.update( + create_job_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] return create_job_kwargs @@ -268,11 +264,11 @@ def _generate_default_job_name( Returns: str: Hybrid job name. """ - max_length = 50 timestamp = timestamp if timestamp is not None else str(int(time.time() * 1000)) if func: name = func.__name__.replace("_", "-") + max_length = 50 if len(name) + len(timestamp) > max_length: name = name[: max_length - len(timestamp) - 1] warnings.warn( @@ -339,8 +335,8 @@ def _process_local_source_module( try: # raises FileNotFoundError if not found abs_path_source_module = Path(source_module).resolve(strict=True) - except FileNotFoundError: - raise ValueError(f"Source module not found: {source_module}") + except FileNotFoundError as e: + raise ValueError(f"Source module not found: {source_module}") from e entry_point = entry_point or abs_path_source_module.stem _validate_entry_point(abs_path_source_module, entry_point) @@ -366,9 +362,8 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: module = importlib.util.find_spec(importable, source_module_path.stem) if module is None: raise AssertionError - # if entry point is nested (ie contains '.'), parent modules are imported - except (ModuleNotFoundError, AssertionError): - raise ValueError(f"Entry point module was not found: {importable}") + except (ModuleNotFoundError, AssertionError) as e: + raise ValueError(f"Entry point module was not found: {importable}") from e finally: sys.path.pop() @@ -460,21 +455,20 @@ def _process_channel( """ if AwsSession.is_s3_uri(location): return S3DataSourceConfig(location) - else: - # local prefix "path/to/prefix" will be mapped to - # s3://bucket/jobs/job-name/subdirectory/data/input/prefix - location_name = Path(location).name - s3_prefix = AwsSession.construct_s3_uri( - aws_session.default_bucket(), - "jobs", - job_name, - subdirectory, - "data", - channel_name, - location_name, - ) - aws_session.upload_local_data(location, s3_prefix) - return S3DataSourceConfig(s3_prefix) + # local prefix "path/to/prefix" will be mapped to + # s3://bucket/jobs/job-name/subdirectory/data/input/prefix + location_name = Path(location).name + s3_prefix = AwsSession.construct_s3_uri( + aws_session.default_bucket(), + "jobs", + job_name, + subdirectory, + "data", + channel_name, + location_name, + ) + aws_session.upload_local_data(location, s3_prefix) + return S3DataSourceConfig(s3_prefix) def _convert_input_to_config(input_data: dict[str, S3DataSourceConfig]) -> list[dict[str, Any]]: diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 78fbe45b3..1f3a69e72 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -66,7 +66,7 @@ def subs(self, parameter_values: dict[str, Number]) -> Union[FreeParameter, Numb Union[FreeParameter, Number]: The substituted value if this parameter is in parameter_values, otherwise returns self """ - return parameter_values[self.name] if self.name in parameter_values else self + return parameter_values.get(self.name, self) def __str__(self): return str(self.name) @@ -92,7 +92,7 @@ def _set_name(self, name: str) -> None: raise ValueError("FreeParameter names must be non empty") if not isinstance(name, str): raise TypeError("FreeParameter names must be strings") - if not name[0].isalpha() and not name[0] == "_": + if not name[0].isalpha() and name[0] != "_": raise ValueError("FreeParameter names must start with a letter or an underscore") self._name = Symbol(name) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index f7ec9d3bf..d2dcf65e8 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -151,9 +151,7 @@ def visit_ClassicalDeclaration( context.variables[identifier] = self.visit(node.init_expression, context) elif type(node.type) == ast.FrameType: pass - elif type(node.type) == ast.PortType: - pass - else: + elif type(node.type) != ast.PortType: raise NotImplementedError def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: @@ -171,7 +169,7 @@ def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseStat # barrier without arguments is applied to all the frames of the context frames = list(context.frame_data.keys()) dts = [context.frame_data[frame_id].dt for frame_id in frames] - max_time = max([context.frame_data[frame_id].current_time for frame_id in frames]) + max_time = max(context.frame_data[frame_id].current_time for frame_id in frames) # All frames are delayed till the first multiple of the LCM([port.dts]) # after the longest time of all considered frames lcm = _lcm_floats(*dts) @@ -198,7 +196,7 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - # barrier without arguments is applied to all the frames of the context frames = list(context.frame_data.keys()) dts = [context.frame_data[frame_id].dt for frame_id in frames] - max_time = max([context.frame_data[frame_id].current_time for frame_id in frames]) + max_time = max(context.frame_data[frame_id].current_time for frame_id in frames) # All frames are delayed till the first multiple of the LCM([port.dts]) # after the longest time of all considered frames lcm = _lcm_floats(*dts) @@ -391,7 +389,7 @@ def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: _ParseState) - Returns: bool: The parsed boolean value. """ - return True if node.value else False + return bool(node.value) def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) -> float: """Visit Duration Literal. @@ -477,7 +475,6 @@ def capture_v0(self, node: ast.FunctionCall, context: _ParseState) -> None: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. """ - pass def play(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'play' Function call. @@ -554,17 +551,16 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: - frame_states = {} - for frameId, frame in frames.items(): - frame_states[frameId] = _FrameState( - frame.port.dt, frame.frequency, frame.phase % (2 * np.pi) - ) + frame_states = { + frameId: _FrameState(frame.port.dt, frame.frequency, frame.phase % (2 * np.pi)) + for frameId, frame in frames.items() + } return frame_states def _init_qubit_frame_mapping(frames: dict[str, Frame]) -> dict[str, list[str]]: mapping = {} - for frameId in frames.keys(): + for frameId in frames: if m := ( re.search(r"q(\d+)_q(\d+)_[a-z_]+", frameId) or re.search(r"[rq](\d+)_[a-z_]+", frameId) ): diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 41c541da8..1750275ac 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -61,12 +61,12 @@ def visit_BinaryExpression( """ lhs = self.visit(node.lhs) rhs = self.visit(node.rhs) - ops = { - ast.BinaryOperator["+"]: operator.add, - ast.BinaryOperator["*"]: operator.mul, - ast.BinaryOperator["**"]: operator.pow, - } if isinstance(lhs, ast.FloatLiteral): + ops = { + ast.BinaryOperator["+"]: operator.add, + ast.BinaryOperator["*"]: operator.mul, + ast.BinaryOperator["**"]: operator.pow, + } if isinstance(rhs, ast.FloatLiteral): return ast.FloatLiteral(ops[node.op](lhs.value, rhs.value)) elif isinstance(rhs, ast.DurationLiteral) and node.op == ast.BinaryOperator["*"]: diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index 3d7adb4a3..40a6d25d5 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -39,22 +39,21 @@ def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatemen Any: The expression statement. """ if ( - isinstance(expression_statement.expression, ast.FunctionCall) - and expression_statement.expression.name.name == "capture_v0" - and self._register_identifier + not isinstance(expression_statement.expression, ast.FunctionCall) + or expression_statement.expression.name.name != "capture_v0" + or not self._register_identifier ): - # For capture_v0 nodes, it replaces it with classical assignment statements - # of the form: - # b[0] = capture_v0(...) - # b[1] = capture_v0(...) - new_val = ast.ClassicalAssignment( - # Ideally should use IndexedIdentifier here, but this works since it is just - # for printing. - ast.Identifier(name=f"{self._register_identifier}[{self._capture_v0_count}]"), - ast.AssignmentOperator["="], - expression_statement.expression, - ) - self._capture_v0_count += 1 - return new_val - else: return expression_statement + # For capture_v0 nodes, it replaces it with classical assignment statements + # of the form: + # b[0] = capture_v0(...) + # b[1] = capture_v0(...) + new_val = ast.ClassicalAssignment( + # Ideally should use IndexedIdentifier here, but this works since it is just + # for printing. + ast.Identifier(name=f"{self._register_identifier}[{self._capture_v0_count}]"), + ast.AssignmentOperator["="], + expression_statement.expression, + ) + self._capture_v0_count += 1 + return new_val diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index a788b38c0..9d43127a0 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -345,7 +345,7 @@ def _parse_arg_from_calibration_schema( "waveform": waveforms.get, "expr": FreeParameterExpression, } - if argument["type"] in nonprimitive_arg_type.keys(): + if argument["type"] in nonprimitive_arg_type: return nonprimitive_arg_type[argument["type"]](argument["value"]) else: return getattr(builtins, argument["type"])(argument["value"]) @@ -369,40 +369,37 @@ def _parse_from_calibration_schema( """ calibration_sequence = cls() for instr in calibration: - if hasattr(PulseSequence, f"{instr['name']}"): - instr_function = getattr(calibration_sequence, instr["name"]) - instr_args_keys = signature(instr_function).parameters.keys() - instr_args = {} - if instr["arguments"] is not None: - for argument in instr["arguments"]: - if argument["name"] in {"qubit", "frame"} and instr["name"] in { - "barrier", - "delay", - }: - argument_value = ( - [frames[argument["value"]]] - if argument["name"] == "frame" - else instr_args.get("qubits_or_frames", QubitSet()) - ) - # QubitSet is an IndexedSet so the ordering matters - if argument["name"] == "frame": - argument_value = ( - instr_args.get("qubits_or_frames", []) + argument_value - ) - else: - argument_value.update(QubitSet(int(argument["value"]))) - instr_args["qubits_or_frames"] = argument_value - elif argument["name"] in instr_args_keys: - instr_args[argument["name"]] = ( - calibration_sequence._parse_arg_from_calibration_schema( - argument, waveforms, frames - ) + if not hasattr(PulseSequence, f"{instr['name']}"): + raise ValueError(f"The {instr['name']} instruction has not been implemented") + instr_function = getattr(calibration_sequence, instr["name"]) + instr_args_keys = signature(instr_function).parameters.keys() + instr_args = {} + if instr["arguments"] is not None: + for argument in instr["arguments"]: + if argument["name"] in {"qubit", "frame"} and instr["name"] in { + "barrier", + "delay", + }: + argument_value = ( + [frames[argument["value"]]] + if argument["name"] == "frame" + else instr_args.get("qubits_or_frames", QubitSet()) + ) + # QubitSet is an IndexedSet so the ordering matters + if argument["name"] == "frame": + argument_value = instr_args.get("qubits_or_frames", []) + argument_value + else: + argument_value.update(QubitSet(int(argument["value"]))) + instr_args["qubits_or_frames"] = argument_value + elif argument["name"] in instr_args_keys: + instr_args[argument["name"]] = ( + calibration_sequence._parse_arg_from_calibration_schema( + argument, waveforms, frames ) - else: - instr_args["qubits_or_frames"] = [] - instr_function(**instr_args) + ) else: - raise ValueError(f"The {instr['name']} instruction has not been implemented") + instr_args["qubits_or_frames"] = [] + instr_function(**instr_args) return calibration_sequence def __call__( diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 9ca43050a..915d187a8 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -512,10 +512,9 @@ def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: "gaussian": GaussianWaveform._from_calibration_schema, "constant": ConstantWaveform._from_calibration_schema, } - if "amplitudes" in waveform.keys(): + if "amplitudes" in waveform: waveform["name"] = "arbitrary" if waveform["name"] in waveform_names: return waveform_names[waveform["name"]](waveform) - else: - id = waveform["waveformId"] - raise ValueError(f"The waveform {id} of cannot be constructed") + waveform_id = waveform["waveformId"] + raise ValueError(f"The waveform {waveform_id} of cannot be constructed") diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 9064b942a..0de8e8e3b 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -208,7 +208,7 @@ def dot(self, other: PauliString, inplace: bool = False) -> PauliString: # ignore complex global phase if phase_result.real < 0 or phase_result.imag < 0: - pauli_result = "-" + pauli_result + pauli_result = f"-{pauli_result}" out_pauli_string = PauliString(pauli_result) if inplace: diff --git a/src/braket/registers/qubit.py b/src/braket/registers/qubit.py index 4be98b640..4c91ebd25 100644 --- a/src/braket/registers/qubit.py +++ b/src/braket/registers/qubit.py @@ -63,7 +63,4 @@ def new(qubit: QubitInput) -> Qubit: Returns: Qubit: The qubit. """ - if isinstance(qubit, Qubit): - return qubit - else: - return Qubit(qubit) + return qubit if isinstance(qubit, Qubit) else Qubit(qubit) diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index 0bc110b2c..7bfb57eb3 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -129,7 +129,7 @@ def get_counts(self) -> dict[str, int]: 0 if pre_i == 0 else 1 if post_i == 0 else 2 for pre_i, post_i in zip(pre, post) ] state = "".join(states[s_idx] for s_idx in state_idx) - state_counts.update((state,)) + state_counts.update([state]) return dict(state_counts) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index b64d4e009..81f90ae7b 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -121,11 +121,11 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: rt_hash = GateModelQuantumTaskResult._result_type_hash(rt_ir) result_type_index = self._result_types_indices[rt_hash] return self.values[result_type_index] - except KeyError: + except KeyError as e: raise ValueError( "Result type not found in result. " "Result types must be added to circuit before circuit is run on device." - ) + ) from e def __eq__(self, other: GateModelQuantumTaskResult) -> bool: if isinstance(other, GateModelQuantumTaskResult): @@ -159,9 +159,9 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: Counter: A Counter of measurements. Key is the measurements in a big endian binary string. Value is the number of times that measurement occurred. """ - bitstrings = [] - for j in range(len(measurements)): - bitstrings.append("".join([str(element) for element in measurements[j]])) + bitstrings = [ + "".join([str(element) for element in measurements[j]]) for j in range(len(measurements)) + ] return Counter(bitstrings) @staticmethod @@ -179,11 +179,11 @@ def measurement_probabilities_from_measurement_counts( dict[str, float]: A dictionary of probabilistic results. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. """ - measurement_probabilities = {} shots = sum(measurement_counts.values()) - for key, count in measurement_counts.items(): - measurement_probabilities[key] = count / shots + measurement_probabilities = { + key: count / shots for key, count in measurement_counts.items() + } return measurement_probabilities @staticmethod @@ -346,13 +346,14 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: if gate_model_task_result.resultTypes: for result_type in gate_model_task_result.resultTypes: type = result_type.type.type - if type == "probability": + if type == "amplitude": + for state in result_type.value: + result_type.value[state] = complex(*result_type.value[state]) + + elif type == "probability": result_type.value = np.array(result_type.value) elif type == "statevector": result_type.value = np.array([complex(*value) for value in result_type.value]) - elif type == "amplitude": - for state in result_type.value: - result_type.value[state] = complex(*result_type.value[state]) @staticmethod def _calculate_result_types( diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 0023f32f5..67797d6c4 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -312,7 +312,7 @@ def periodic_signal(times: list[float], values: list[float], num_repeat: int = 1 Returns: TimeSeries: A new periodic time series. """ - if not (values[0] == values[-1]): + if values[0] != values[-1]: raise ValueError("The first and last values must coincide to guarantee periodicity") new_time_series = TimeSeries() diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 41f20da8e..901cc819e 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -13,7 +13,7 @@ import concurrent.futures import math -from typing import Any, Dict, Union +from typing import Any, Union import numpy as np @@ -26,11 +26,11 @@ from braket.tasks import GateModelQuantumTaskResult -def get_tol(shots: int) -> Dict[str, float]: +def get_tol(shots: int) -> dict[str, float]: return {"atol": 0.2, "rtol": 0.3} if shots else {"atol": 0.01, "rtol": 0} -def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): +def qubit_ordering_testing(device: Device, run_kwargs: dict[str, Any]): # |110> should get back value of "110" state_110 = Circuit().x(0).x(1).i(2) result = device.run(state_110, **run_kwargs).result() @@ -51,8 +51,8 @@ def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): def no_result_types_testing( program: Union[Circuit, OpenQasmProgram], device: Device, - run_kwargs: Dict[str, Any], - expected: Dict[str, float], + run_kwargs: dict[str, Any], + expected: dict[str, float], ): shots = run_kwargs["shots"] tol = get_tol(shots) @@ -63,14 +63,14 @@ def no_result_types_testing( assert len(result.measurements) == shots -def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def no_result_types_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): bell = Circuit().h(0).cnot(0, 1) bell_qasm = bell.to_ir(ir_type=IRType.OPENQASM) for task in (bell, bell_qasm): no_result_types_testing(task, device, run_kwargs, {"00": 0.5, "11": 0.5}) -def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict[str, Any]): +def result_types_observable_not_in_instructions(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) bell = ( @@ -90,7 +90,7 @@ def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict def result_types_zero_shots_bell_pair_testing( device: Device, include_state_vector: bool, - run_kwargs: Dict[str, Any], + run_kwargs: dict[str, Any], include_amplitude: bool = True, ): circuit = ( @@ -128,7 +128,7 @@ def result_types_zero_shots_bell_pair_testing( assert np.isclose(amplitude["11"], 1 / np.sqrt(2)) -def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().h(0).cnot(0, 1).probability() @@ -143,7 +143,7 @@ def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: ) -def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().h(0).cnot(0, 1).probability(0) @@ -158,7 +158,7 @@ def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwar ) -def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): circuit = ( Circuit() .h(0) @@ -188,7 +188,7 @@ def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dic def result_types_hermitian_testing( - device: Device, run_kwargs: Dict[str, Any], test_program: bool = True + device: Device, run_kwargs: dict[str, Any], test_program: bool = True ): shots = run_kwargs["shots"] theta = 0.543 @@ -202,7 +202,7 @@ def result_types_hermitian_testing( ) if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 0)) - tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) if test_program else (circuit,) for task in tasks: result = device.run(task, **run_kwargs).result() @@ -215,7 +215,7 @@ def result_types_hermitian_testing( def result_types_all_selected_testing( - device: Device, run_kwargs: Dict[str, Any], test_program: bool = True + device: Device, run_kwargs: dict[str, Any], test_program: bool = True ): shots = run_kwargs["shots"] theta = 0.543 @@ -231,7 +231,7 @@ def result_types_all_selected_testing( if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 1)) - tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) if test_program else (circuit,) for task in tasks: result = device.run(task, **run_kwargs).result() @@ -280,7 +280,7 @@ def assert_variance_expectation_sample_result( assert np.allclose(variance, expected_var, **tol) -def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_x_y_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -308,7 +308,7 @@ def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): ) -def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_z_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -329,7 +329,7 @@ def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): ) -def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -359,7 +359,7 @@ def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: ) -def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -386,7 +386,7 @@ def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any] ) -def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -450,7 +450,7 @@ def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str ) -def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -479,7 +479,7 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str ) -def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any]): shots = 0 theta = 0.432 phi = 0.123 @@ -525,7 +525,7 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any] assert np.allclose(result.values[3], expected_mean3) -def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: dict[str, Any]): circuit = ( Circuit() .h(0) @@ -540,7 +540,7 @@ def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs assert np.allclose(result.values[1], np.sqrt(2) / 2) -def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_all(device: Device, run_kwargs: dict[str, Any]): array = np.array([[1, 2j], [-2j, 0]]) circuit = ( Circuit() @@ -556,7 +556,7 @@ def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): assert np.allclose(result.values[1], [0, 0]) -def multithreaded_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def multithreaded_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) bell = Circuit().h(0).cnot(0, 1) @@ -581,7 +581,7 @@ def run_circuit(circuit): assert len(result.measurements) == shots -def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: Dict[str, Any]): +def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().x(0).x(1).bit_flip(0, 0.1).probability() @@ -596,7 +596,7 @@ def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: Dict ) -def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict[str, Any]): +def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) K0 = np.eye(4) * np.sqrt(0.9) @@ -615,7 +615,7 @@ def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict ) -def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): +def batch_bell_pair_testing(device: AwsDevice, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuits = [Circuit().h(0).cnot(0, 1) for _ in range(10)] @@ -630,7 +630,7 @@ def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): assert [task.result() for task in batch.tasks] == results -def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): +def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: dict[str, Any]): openqasm_string = ( "OPENQASM 3;" "qubit[2] q;" @@ -649,7 +649,7 @@ def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): def openqasm_noisy_circuit_1qubit_noise_full_probability( - device: Device, run_kwargs: Dict[str, Any] + device: Device, run_kwargs: dict[str, Any] ): shots = run_kwargs["shots"] tol = get_tol(shots) @@ -675,7 +675,7 @@ def openqasm_noisy_circuit_1qubit_noise_full_probability( ) -def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): openqasm_string = ( "OPENQASM 3;" "qubit[2] q;" diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py index 42303465e..d2a74e6cc 100644 --- a/test/integ_tests/job_test_script.py +++ b/test/integ_tests/job_test_script.py @@ -43,7 +43,7 @@ def completed_job_script(): device = AwsDevice(get_job_device_arn()) bell = Circuit().h(0).cnot(0, 1) - for count in range(3): + for _ in range(3): task = device.run(bell, shots=10) print(task.result().measurement_counts) save_job_result({"converged": True, "energy": -0.2}) diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index 8ceae35cd..16c001f35 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -81,7 +81,7 @@ def test_completed_local_job(aws_session, capsys): }, ), ]: - with open(file_name, "r") as f: + with open(file_name) as f: assert json.loads(f.read()) == expected_data # Capture logs diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 8b2eae758..be0e49a85 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -63,7 +63,7 @@ def test_failed_quantum_job(aws_session, capsys, failed_quantum_job): subdirectory = re.match( rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], - ).group(1) + )[1] keys = aws_session.list_keys( bucket=s3_bucket, prefix=f"jobs/{job_name}/{subdirectory}/", @@ -119,7 +119,7 @@ def test_completed_quantum_job(aws_session, capsys, completed_quantum_job): subdirectory = re.match( rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], - ).group(1) + )[1] keys = aws_session.list_keys( bucket=s3_bucket, prefix=f"jobs/{job_name}/{subdirectory}/", @@ -220,9 +220,9 @@ def __str__(self): input_data=str(Path("test", "integ_tests", "requirements")), ) def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): - with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f: + with open(Path(get_input_data_dir()) / "requirements.txt") as f: assert f.readlines() == ["pytest\n"] - with open(Path("test", "integ_tests", "requirements.txt"), "r") as f: + with open(Path("test", "integ_tests", "requirements.txt")) as f: assert f.readlines() == ["pytest\n"] assert dir(pytest) assert a.attribute == "value" @@ -232,7 +232,7 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): assert extras["extra_arg"] == "extra_value" hp_file = os.environ["AMZN_BRAKET_HP_FILE"] - with open(hp_file, "r") as f: + with open(hp_file) as f: hyperparameters = json.load(f) assert hyperparameters == { "a": "MyClass{value}", @@ -255,7 +255,7 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): os.chdir(temp_dir) try: job.download_result() - with open(Path(job.name, "test", "output_file.txt"), "r") as f: + with open(Path(job.name, "test", "output_file.txt")) as f: assert f.read() == "hello" assert ( Path(job.name, "results.json").exists() @@ -286,12 +286,12 @@ def test_decorator_job_submodule(): }, ) def decorator_job_submodule(): - with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f: + with open(Path(get_input_data_dir("my_input")) / "requirements.txt") as f: assert f.readlines() == ["pytest\n"] - with open(Path("test", "integ_tests", "requirements.txt"), "r") as f: + with open(Path("test", "integ_tests", "requirements.txt")) as f: assert f.readlines() == ["pytest\n"] with open( - Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r" + Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt" ) as f: assert f.readlines() == ["pytest\n"] with open( @@ -302,7 +302,6 @@ def decorator_job_submodule(): "job_test_submodule", "requirements.txt", ), - "r", ) as f: assert f.readlines() == ["pytest\n"] assert dir(pytest) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 74b40afb6..540c09f61 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Set import pytest @@ -108,15 +107,14 @@ def _get_device_name(device: AwsDevice) -> str: return device_name -def _get_active_providers(aws_devices: List[AwsDevice]) -> Set[str]: - active_providers = set() - for device in aws_devices: - if device.status != "RETIRED": - active_providers.add(_get_provider_name(device)) +def _get_active_providers(aws_devices: list[AwsDevice]) -> set[str]: + active_providers = { + _get_provider_name(device) for device in aws_devices if device.status != "RETIRED" + } return active_providers -def _validate_device(device: AwsDevice, active_providers: Set[str]): +def _validate_device(device: AwsDevice, active_providers: set[str]): provider_name = _get_provider_name(device) if provider_name not in active_providers: provider_name = f"_{provider_name}" diff --git a/test/unit_tests/braket/ahs/test_atom_arrangement.py b/test/unit_tests/braket/ahs/test_atom_arrangement.py index 425458547..06a926163 100644 --- a/test/unit_tests/braket/ahs/test_atom_arrangement.py +++ b/test/unit_tests/braket/ahs/test_atom_arrangement.py @@ -52,9 +52,7 @@ def test_iteration(): atom_arrangement = AtomArrangement() for value in values: atom_arrangement.add(value) - returned_values = [] - for site in atom_arrangement: - returned_values.append(site.coordinate) + returned_values = [site.coordinate for site in atom_arrangement] assert values == returned_values diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index d9d6c3d44..aaca559f5 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -222,7 +222,7 @@ def run_and_assert( run_args.append(inputs) if gate_definitions is not None: run_args.append(gate_definitions) - run_args += extra_args if extra_args else [] + run_args += extra_args or [] run_kwargs = extra_kwargs or {} if reservation_arn: run_kwargs.update({"reservation_arn": reservation_arn}) @@ -295,7 +295,7 @@ def run_batch_and_assert( run_args.append(inputs) if gate_definitions is not None: run_args.append(gate_definitions) - run_args += extra_args if extra_args else [] + run_args += extra_args or [] run_kwargs = extra_kwargs or {} if reservation_arn: run_kwargs.update({"reservation_arn": reservation_arn}) @@ -350,7 +350,7 @@ def _create_task_args_and_kwargs( s3_folder if s3_folder is not None else default_s3_folder, shots if shots is not None else default_shots, ] - create_args += extra_args if extra_args else [] + create_args += extra_args or [] create_kwargs = extra_kwargs or {} create_kwargs.update( { diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index a85ca6eb9..7f9e6179d 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -833,7 +833,7 @@ def test_gate_calibration_refresh_no_url(arn): mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) - assert device.refresh_gate_calibrations() == None + assert device.refresh_gate_calibrations() is None @patch("urllib.request.urlopen") @@ -945,7 +945,7 @@ def test_repr(arn): mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) - expected = "Device('name': {}, 'arn': {})".format(device.name, device.arn) + expected = f"Device('name': {device.name}, 'arn': {device.arn})" assert repr(device) == expected @@ -1882,7 +1882,8 @@ def test_get_devices_invalid_order_by(): @patch("braket.aws.aws_device.datetime") def test_get_device_availability(mock_utc_now): - class Expando(object): + + class Expando: pass class MockDevice(AwsDevice): @@ -1890,19 +1891,18 @@ def __init__(self, status, *execution_window_args): self._status = status self._properties = Expando() self._properties.service = Expando() - execution_windows = [] - for execution_day, window_start_hour, window_end_hour in execution_window_args: - execution_windows.append( - DeviceExecutionWindow.parse_raw( - json.dumps( - { - "executionDay": execution_day, - "windowStartHour": window_start_hour, - "windowEndHour": window_end_hour, - } - ) + execution_windows = [ + DeviceExecutionWindow.parse_raw( + json.dumps( + { + "executionDay": execution_day, + "windowStartHour": window_start_hour, + "windowEndHour": window_end_hour, + } ) ) + for execution_day, window_start_hour, window_end_hour in execution_window_args + ] self._properties.service.executionWindows = execution_windows test_sets = ( @@ -2041,7 +2041,7 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): def test_device_no_href(): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - device = AwsDevice(DWAVE_ARN, mock_session) + AwsDevice(DWAVE_ARN, mock_session) def test_parse_calibration_data(): diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 8df2118cf..67ca98228 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -364,7 +364,7 @@ def test_download_result_when_extract_path_not_provided( job_name = job_metadata["jobName"] quantum_job.download_result() - with open(f"{job_name}/results.json", "r") as file: + with open(f"{job_name}/results.json") as file: actual_data = json.loads(file.read())["dataDictionary"] assert expected_saved_data == actual_data @@ -382,7 +382,7 @@ def test_download_result_when_extract_path_provided( with tempfile.TemporaryDirectory() as temp_dir: quantum_job.download_result(temp_dir) - with open(f"{temp_dir}/{job_name}/results.json", "r") as file: + with open(f"{temp_dir}/{job_name}/results.json") as file: actual_data = json.loads(file.read())["dataDictionary"] assert expected_saved_data == actual_data diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 6e789ec92..16a72da7a 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -172,7 +172,7 @@ def test_equality(arn, aws_session): def test_str(quantum_task): - expected = "AwsQuantumTask('id/taskArn':'{}')".format(quantum_task.id) + expected = f"AwsQuantumTask('id/taskArn':'{quantum_task.id}')" assert str(quantum_task) == expected @@ -1216,20 +1216,16 @@ def _assert_create_quantum_task_called_with( } if device_parameters is not None: - test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) + test_kwargs["deviceParameters"] = device_parameters.json(exclude_none=True) if tags is not None: - test_kwargs.update({"tags": tags}) + test_kwargs["tags"] = tags if reservation_arn: - test_kwargs.update( + test_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] aws_session.create_quantum_task.assert_called_with(**test_kwargs) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 56d23b2e9..fb0b309fb 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -410,7 +410,7 @@ def test_create_quantum_task_with_job_token(aws_session): } with patch.dict(os.environ, {"AMZN_BRAKET_JOB_TOKEN": job_token}): assert aws_session.create_quantum_task(**kwargs) == arn - kwargs.update({"jobToken": job_token}) + kwargs["jobToken"] = job_token aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) @@ -1284,10 +1284,10 @@ def test_describe_log_streams(aws_session, limit, next_token): } if limit: - describe_log_stream_args.update({"limit": limit}) + describe_log_stream_args["limit"] = limit if next_token: - describe_log_stream_args.update({"nextToken": next_token}) + describe_log_stream_args["nextToken"] = next_token aws_session.describe_log_streams(log_group, log_stream_prefix, limit, next_token) @@ -1314,7 +1314,7 @@ def test_get_log_events(aws_session, next_token): } if next_token: - log_events_args.update({"nextToken": next_token}) + log_events_args["nextToken"] = next_token aws_session.get_log_events(log_group, log_stream_name, start_time, start_from_head, next_token) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index ae33d4029..4c5252c02 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -131,7 +131,7 @@ def test_np_float_angle_json(): angled_gate = AngledGate(angle=np.float32(0.15), qubit_count=1, ascii_symbols=["foo"]) angled_gate_json = BaseModel.construct(target=[0], angle=angled_gate.angle).json() match = re.match(r'\{"target": \[0], "angle": (\d*\.?\d*)}', angled_gate_json) - angle_value = float(match.group(1)) + angle_value = float(match[1]) assert angle_value == angled_gate.angle diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index c0c58ad35..71eecd1f1 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -245,9 +245,7 @@ def test_call_one_param_not_bound(): circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0) new_circ = circ(theta=1) expected_circ = Circuit().h(0).rx(angle=1, target=1).ry(angle=alpha, target=0) - expected_parameters = set() - expected_parameters.add(alpha) - + expected_parameters = {alpha} assert new_circ == expected_circ and new_circ.parameters == expected_parameters @@ -3219,9 +3217,7 @@ def test_add_parameterized_check_true(): .ry(angle=theta, target=2) .ry(angle=theta, target=3) ) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3231,10 +3227,7 @@ def test_add_parameterized_instr_parameterized_circ_check_true(): alpha2 = FreeParameter("alpha") circ = Circuit().ry(angle=theta, target=0).ry(angle=alpha2, target=1).ry(angle=theta, target=2) circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) - expected = set() - expected.add(theta) - expected.add(alpha) - + expected = {theta, alpha} assert circ.parameters == expected @@ -3242,9 +3235,7 @@ def test_add_non_parameterized_instr_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) circ.add_instruction(Instruction(Gate.Ry(0.1), 3)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3252,9 +3243,7 @@ def test_add_circ_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=1, target=0).add_circuit(Circuit().ry(angle=theta, target=0)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3262,9 +3251,7 @@ def test_add_circ_not_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).add_circuit(Circuit().ry(angle=0.1, target=0)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3285,9 +3272,7 @@ def test_parameterized_check_false(input_circ): def test_parameters(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3345,9 +3330,7 @@ def test_make_bound_circuit_partial_bind(): expected_circ = ( Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=alpha, target=2) ) - expected_parameters = set() - expected_parameters.add(alpha) - + expected_parameters = {alpha} assert circ_new == expected_circ and circ_new.parameters == expected_parameters @@ -3552,7 +3535,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): Circuit().rx(angle=theta, target=0).pulse_gate(pulse_sequence=pulse_sequence, targets=1) ) - assert circuit.parameters == set([frequency_parameter, length, theta]) + assert circuit.parameters == {frequency_parameter, length, theta} bound_half = circuit(theta=0.5, length=1e-5) assert bound_half.to_ir( diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 3a7e621df..0b9ce52d7 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -250,21 +250,20 @@ def two_dimensional_matrix_valid_input(**kwargs): def create_valid_ir_input(irsubclasses): input = {} for subclass in irsubclasses: - input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")()) + input |= valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")() return input def create_valid_subroutine_input(irsubclasses, **kwargs): input = {} for subclass in irsubclasses: - input.update( - valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")(**kwargs) + input |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( + **kwargs ) return input def create_valid_target_input(irsubclasses): - input = {} qubit_set = [] control_qubit_set = [] control_state = None @@ -285,11 +284,9 @@ def create_valid_target_input(irsubclasses): control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): - pass - else: + elif subclass not in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): raise ValueError("Invalid subclass") - input["target"] = QubitSet(qubit_set) + input = {"target": QubitSet(qubit_set)} input["control"] = QubitSet(control_qubit_set) input["control_state"] = control_state return input @@ -327,9 +324,13 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): - pass - else: + elif subclass not in ( + NoTarget, + Angle, + TwoDimensionalMatrix, + DoubleAngle, + TripleAngle, + ): raise ValueError("Invalid subclass") return qubit_count @@ -916,14 +917,13 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar ) if qubit_count == 1: multi_targets = [0, 1, 2] - instruction_list = [] - for target in multi_targets: - instruction_list.append( - Instruction( - operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), - target=target, - ) + instruction_list = [ + Instruction( + operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), + target=target, ) + for target in multi_targets + ] subroutine = getattr(Circuit(), subroutine_name) subroutine_input = {"target": multi_targets} if Angle in irsubclasses: diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 212e65949..1d04627e9 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -107,14 +107,11 @@ def test_adjoint_unsupported(): def test_str(instr): expected = ( - "Instruction('operator': {}, 'target': {}, " - "'control': {}, 'control_state': {}, 'power': {})" - ).format( - instr.operator, - instr.target, - instr.control, - instr.control_state.as_tuple, - instr.power, + f"Instruction('operator': {instr.operator}, " + f"'target': {instr.target}, " + f"'control': {instr.control}, " + f"'control_state': {instr.control_state.as_tuple}, " + f"'power': {instr.power})" ) assert str(instr) == expected diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index 982649f62..ed45b0aa3 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -153,7 +153,7 @@ def test_getitem(): def test_iter(moments): - assert [key for key in moments] == list(moments.keys()) + assert list(moments) == list(moments.keys()) def test_len(): diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index a9bd3f5a3..5fffba43f 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -232,21 +232,20 @@ def multi_probability_invalid_input(**kwargs): def create_valid_ir_input(irsubclasses): input = {} for subclass in irsubclasses: - input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")()) + input |= valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")() return input def create_valid_subroutine_input(irsubclasses, **kwargs): input = {} for subclass in irsubclasses: - input.update( - valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")(**kwargs) + input |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( + **kwargs ) return input def create_valid_target_input(irsubclasses): - input = {} qubit_set = [] # based on the concept that control goes first in target input for subclass in irsubclasses: @@ -260,8 +259,8 @@ def create_valid_target_input(irsubclasses): qubit_set = list(single_control_valid_input().values()) + qubit_set elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif any( - subclass == i + elif all( + subclass != i for i in [ SingleProbability, SingleProbability_34, @@ -273,17 +272,15 @@ def create_valid_target_input(irsubclasses): MultiProbability, ] ): - pass - else: raise ValueError("Invalid subclass") - input["target"] = QubitSet(qubit_set) + input = {"target": QubitSet(qubit_set)} return input def create_valid_noise_class_input(irsubclasses, **kwargs): input = {} if SingleProbability in irsubclasses: - input.update(single_probability_valid_input()) + input |= single_probability_valid_input() if SingleProbability_34 in irsubclasses: input.update(single_probability_34_valid_input()) if SingleProbability_1516 in irsubclasses: @@ -320,8 +317,8 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif any( - subclass == i + elif all( + subclass != i for i in [ SingleProbability, SingleProbability_34, @@ -333,8 +330,6 @@ def calculate_qubit_count(irsubclasses): TwoDimensionalMatrixList, ] ): - pass - else: raise ValueError("Invalid subclass") return qubit_count @@ -365,18 +360,17 @@ def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwa ) if qubit_count == 1: multi_targets = [0, 1, 2] - instruction_list = [] - for target in multi_targets: - instruction_list.append( - Instruction( - operator=testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)), - target=target, - ) + instruction_list = [ + Instruction( + operator=testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)), + target=target, ) + for target in multi_targets + ] subroutine = getattr(Circuit(), subroutine_name) subroutine_input = {"target": multi_targets} if SingleProbability in irsubclasses: - subroutine_input.update(single_probability_valid_input()) + subroutine_input |= single_probability_valid_input() if SingleProbability_34 in irsubclasses: subroutine_input.update(single_probability_34_valid_input()) if SingleProbability_1516 in irsubclasses: diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index b7e9c201f..38689398a 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -130,7 +130,7 @@ def test_eigenvalue_not_implemented_by_default(observable): def test_str(observable): - expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) + expected = f"{observable.name}('qubit_count': {observable.qubit_count})" assert str(observable) == expected assert observable.coefficient == 1 diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 79f917af9..b6430d4d8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -497,7 +497,7 @@ def test_flattened_tensor_product(): def test_hermitian_basis_rotation_gates(matrix, basis_rotation_matrix): expected_unitary = Gate.Unitary(matrix=basis_rotation_matrix) actual_rotation_gates = Observable.Hermitian(matrix=matrix).basis_rotation_gates - assert actual_rotation_gates == tuple([expected_unitary]) + assert actual_rotation_gates == (expected_unitary,) assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) @@ -596,16 +596,16 @@ def test_tensor_product_eigenvalues(observable, eigenvalues): @pytest.mark.parametrize( "observable,basis_rotation_gates", [ - (Observable.X() @ Observable.Y(), tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()])), + (Observable.X() @ Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H())), ( Observable.X() @ Observable.Y() @ Observable.Z(), - tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), ), ( Observable.X() @ Observable.Y() @ Observable.I(), - tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), ), - (Observable.X() @ Observable.H(), tuple([Gate.H(), Gate.Ry(-np.pi / 4)])), + (Observable.X() @ Observable.H(), (Gate.H(), Gate.Ry(-np.pi / 4))), ], ) def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): @@ -642,9 +642,7 @@ def test_sum_not_allowed_in_tensor_product(): @pytest.mark.parametrize( "observable,basis_rotation_gates", - [ - (Observable.X() + Observable.Y(), tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()])), - ], + [(Observable.X() + Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H()))], ) def test_no_basis_rotation_support_for_sum(observable, basis_rotation_gates): no_basis_rotation_support_for_sum = "Basis rotation calculation not supported for Sum" diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index ed58d5d63..3a26e8c82 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -138,5 +138,5 @@ def test_matrix_equivalence_non_quantum_operator(): def test_str(quantum_operator): - expected = "{}('qubit_count': {})".format(quantum_operator.name, quantum_operator.qubit_count) + expected = f"{quantum_operator.name}('qubit_count': {quantum_operator.qubit_count})" assert str(quantum_operator) == expected diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index f284b5a6f..a7e8bfe17 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -14,7 +14,7 @@ import json import textwrap import warnings -from typing import Any, Dict, Optional +from typing import Any, Optional from unittest.mock import Mock, patch import pytest @@ -116,10 +116,10 @@ def run( program: ir.jaqcd.Program, qubits: int, shots: Optional[int], - inputs: Optional[Dict[str, float]], + inputs: Optional[dict[str, float]], *args, **kwargs, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: self._shots = shots self._qubits = qubits return GATE_MODEL_RESULT @@ -156,7 +156,7 @@ def properties(self) -> DeviceCapabilities: class DummyJaqcdSimulator(BraketSimulator): def run( self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs - ) -> Dict[str, Any]: + ) -> dict[str, Any]: if not isinstance(program, ir.jaqcd.Program): raise TypeError("Not a Jaqcd program") self._shots = shots @@ -253,7 +253,7 @@ def properties(self) -> DeviceCapabilities: class DummyProgramDensityMatrixSimulator(BraketSimulator): def run( self, program: ir.openqasm.Program, shots: Optional[int], *args, **kwargs - ) -> Dict[str, Any]: + ) -> dict[str, Any]: self._shots = shots return GATE_MODEL_RESULT diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py index fdaff840b..247d1873b 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py @@ -128,8 +128,6 @@ def test_get_metrics_timeout(mock_add_metrics, mock_get_metrics, aws_session): def get_log_events_forever(*args, **kwargs): - next_token = "1" token = kwargs.get("nextToken") - if token and token == "1": - next_token = "2" + next_token = "2" if token and token == "1" else "1" return {"events": EXAMPLE_METRICS_LOG_LINES, "nextForwardToken": next_token} diff --git a/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py index 6a5e27283..a4ac78f26 100644 --- a/test/unit_tests/braket/jobs/test_data_persistence.py +++ b/test/unit_tests/braket/jobs/test_data_persistence.py @@ -83,7 +83,7 @@ def test_save_job_checkpoint( if file_suffix else f"{tmp_dir}/{job_name}.json" ) - with open(expected_file_location, "r") as expected_file: + with open(expected_file_location) as expected_file: assert expected_file.read() == expected_saved_data @@ -267,7 +267,7 @@ def test_save_job_result(data_format, result_data, expected_saved_data): save_job_result(result_data, data_format) expected_file_location = f"{tmp_dir}/results.json" - with open(expected_file_location, "r") as expected_file: + with open(expected_file_location) as expected_file: assert expected_file.read() == expected_saved_data diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index 592af6840..b1739d879 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -511,7 +511,7 @@ def my_entry(*args, **kwargs): args, kwargs = (1, "two"), {"three": 3} template = _serialize_entry_point(my_entry, args, kwargs) - pickled_str = re.search(r"(?s)cloudpickle.loads\((.*?)\)\ndef my_entry", template).group(1) + pickled_str = re.search(r"(?s)cloudpickle.loads\((.*?)\)\ndef my_entry", template)[1] byte_str = ast.literal_eval(pickled_str) recovered = cloudpickle.loads(byte_str) diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py index 8cd1fbca9..d12a29b00 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -148,9 +148,7 @@ def data_parallel(request): @pytest.fixture def distribution(data_parallel): - if data_parallel: - return "data_parallel" - return None + return "data_parallel" if data_parallel else None @pytest.fixture @@ -255,8 +253,8 @@ def create_job_args( reservation_arn, ): if request.param == "fixtures": - return dict( - (key, value) + return { + key: value for key, value in { "device": device, "source_module": source_module, @@ -277,7 +275,7 @@ def create_job_args( "reservation_arn": reservation_arn, }.items() if value is not None - ) + } elif request.param == "defaults": return { "device": device, @@ -339,7 +337,7 @@ def _translate_creation_args(create_job_args): "sagemaker_distributed_dataparallel_enabled": "true", "sagemaker_instance_type": instance_config.instanceType, } - hyperparameters.update(distributed_hyperparams) + hyperparameters |= distributed_hyperparams output_data_config = create_job_args["output_data_config"] or OutputDataConfig( s3Path=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, timestamp, "data") ) @@ -379,16 +377,12 @@ def _translate_creation_args(create_job_args): } if reservation_arn: - test_kwargs.update( + test_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] return test_kwargs diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py index c4199e81b..56f02aa12 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Union +from typing import Union from unittest.mock import Mock import numpy as np @@ -85,7 +85,7 @@ def test_delay_multiple_frames(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["frame1"].put(shift_time_frame1, 0).put( @@ -104,7 +104,7 @@ def test_delay_multiple_frames(port): expected_phases["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -156,7 +156,7 @@ def test_delay_qubits(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( @@ -177,7 +177,7 @@ def test_delay_qubits(port): expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -230,7 +230,7 @@ def test_delay_no_args(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( @@ -251,7 +251,7 @@ def test_delay_no_args(port): expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -636,7 +636,7 @@ def test_play_drag_gaussian_waveforms(port): dtype=np.complex128, ) - shift_time = shift_time + 20e-9 + shift_time += 20e-9 for t, v in zip(times, values): expected_amplitudes["frame1"].put(t + shift_time, v) expected_frequencies["frame1"].put(t + shift_time, 1e8) @@ -681,7 +681,7 @@ def test_barrier_same_dt(port): expected_phases["frame2"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -739,7 +739,7 @@ def test_barrier_no_args(port): expected_phases["frame2"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -797,7 +797,7 @@ def test_barrier_qubits(port): expected_phases["q0_q1_frame"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -1001,8 +1001,8 @@ def verify_results(results, expected_amplitudes, expected_frequencies, expected_ assert _all_close(results.phases[frame_id], expected_phases[frame_id], 1e-10) -def to_dict(frames: Union[Frame, List]): - if not isinstance(frames, List): +def to_dict(frames: Union[Frame, list]): + if not isinstance(frames, list): frames = [frames] frame_dict = dict() for frame in frames: diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 0c56d3542..34f989c0b 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -33,10 +33,10 @@ ], ) def test_arbitrary_waveform(amps): - id = "arb_wf_x" - wf = ArbitraryWaveform(amps, id) + waveform_id = "arb_wf_x" + wf = ArbitraryWaveform(amps, waveform_id) assert wf.amplitudes == list(amps) - assert wf.id == id + assert wf.id == waveform_id oq_exp = wf._to_oqpy_expression() assert oq_exp.init_expression == list(amps) assert oq_exp.name == wf.id @@ -44,8 +44,8 @@ def test_arbitrary_waveform(amps): def test_arbitrary_waveform_repr(): amps = [1, 4, 5] - id = "arb_wf_x" - wf = ArbitraryWaveform(amps, id) + waveform_id = "arb_wf_x" + wf = ArbitraryWaveform(amps, waveform_id) expected = f"ArbitraryWaveform('id': {wf.id}, 'amplitudes': {wf.amplitudes})" assert repr(wf) == expected diff --git a/test/unit_tests/braket/registers/test_qubit.py b/test/unit_tests/braket/registers/test_qubit.py index 98f89cf8d..0c04a5d95 100644 --- a/test/unit_tests/braket/registers/test_qubit.py +++ b/test/unit_tests/braket/registers/test_qubit.py @@ -39,7 +39,7 @@ def test_index_gte_zero(qubit_index): def test_str(qubit): - expected = "Qubit({})".format(int(qubit)) + expected = f"Qubit({int(qubit)})" assert str(qubit) == expected diff --git a/test/unit_tests/braket/registers/test_qubit_set.py b/test/unit_tests/braket/registers/test_qubit_set.py index 1fd8d7212..1d730b967 100644 --- a/test/unit_tests/braket/registers/test_qubit_set.py +++ b/test/unit_tests/braket/registers/test_qubit_set.py @@ -31,20 +31,20 @@ def test_default_input(): def test_with_single(): - assert QubitSet(0) == tuple([Qubit(0)]) + assert QubitSet(0) == (Qubit(0),) def test_with_iterable(): - assert QubitSet([0, 1]) == tuple([Qubit(0), Qubit(1)]) + assert QubitSet([0, 1]) == (Qubit(0), Qubit(1)) def test_with_nested_iterable(): - assert QubitSet([0, 1, [2, 3]]) == tuple([Qubit(0), Qubit(1), Qubit(2), Qubit(3)]) + assert QubitSet([0, 1, [2, 3]]) == (Qubit(0), Qubit(1), Qubit(2), Qubit(3)) def test_with_qubit_set(): qubits = QubitSet([0, 1]) - assert QubitSet([qubits, [2, 3]]) == tuple([Qubit(0), Qubit(1), Qubit(2), Qubit(3)]) + assert QubitSet([qubits, [2, 3]]) == (Qubit(0), Qubit(1), Qubit(2), Qubit(3)) def test_flattening_does_not_recurse_infinitely(): diff --git a/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py b/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py index 29d21e58f..03f68b7cb 100644 --- a/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py @@ -221,7 +221,7 @@ def test_data_sort_by_none(annealing_result, solutions, values, solution_counts) def test_data_selected_fields(annealing_result, solutions, values, solution_counts): d = list(annealing_result.data(selected_fields=["value"])) for i in range(len(solutions)): - assert d[i] == tuple([values[i]]) + assert d[i] == (values[i],) def test_data_reverse(annealing_result, solutions, values, solution_counts): diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 43dd06db7..5447bd8b2 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -405,7 +405,7 @@ def test_from_string_measurement_probabilities(result_str_3): measurement_list = [list("011000") for _ in range(shots)] expected_measurements = np.asarray(measurement_list, dtype=int) assert np.allclose(task_result.measurements, expected_measurements) - assert task_result.measurement_counts == Counter(["011000" for x in range(shots)]) + assert task_result.measurement_counts == Counter(["011000" for _ in range(shots)]) assert not task_result.measurement_counts_copied_from_device assert task_result.measurement_probabilities_copied_from_device assert not task_result.measurements_copied_from_device diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task.py b/test/unit_tests/braket/tasks/test_local_quantum_task.py index 6b583c608..aca0aa20d 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -57,5 +57,5 @@ def test_async(): def test_str(): - expected = "LocalQuantumTask('id':{})".format(TASK.id) + expected = f"LocalQuantumTask('id':{TASK.id})" assert str(TASK) == expected diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py index c5a26334f..729813aa0 100755 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -304,10 +304,10 @@ def test_discretize_values(default_time_series, value_res, expected_values): [ (TimeSeries(), TimeSeries(), True), (TimeSeries().put(0.1, 0.2), TimeSeries(), False), - (TimeSeries().put(float(0.1), float(0.2)), TimeSeries().put(float(0.1), float(0.2)), True), - (TimeSeries().put(float(1), float(0.2)), TimeSeries().put(int(1), float(0.2)), True), - (TimeSeries().put(float(0.1), float(0.2)), TimeSeries().put(float(0.2), float(0.2)), False), - (TimeSeries().put(float(0.1), float(0.3)), TimeSeries().put(float(0.1), float(0.2)), False), + (TimeSeries().put(0.1, 0.2), TimeSeries().put(0.1, 0.2), True), + (TimeSeries().put(float(1), 0.2), TimeSeries().put(1, 0.2), True), + (TimeSeries().put(0.1, 0.2), TimeSeries().put(0.2, 0.2), False), + (TimeSeries().put(0.1, 0.3), TimeSeries().put(0.1, 0.2), False), ], ) def test_all_close(first_series, second_series, expected_result): From c1a941fef3444d08b1d28eb69d0c9d3a3ae27972 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Apr 2024 16:19:02 +0000 Subject: [PATCH 152/347] prepare release v1.78.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a91e3d8c..4cb8ae8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.78.0 (2024-04-18) + +### Features + + * add phase RX gate + ## v1.77.6 (2024-04-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 07aeada44..d1dfdba08 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.7.dev0" +__version__ = "1.78.0" From 07d9e1e9408fb1f254b3f008d304194d991dd3d6 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Apr 2024 16:19:02 +0000 Subject: [PATCH 153/347] update development version to v1.78.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d1dfdba08..fec1b4f08 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.78.0" +__version__ = "1.78.1.dev0" From d9662227874f23d3b85622254e242dbba15569dc Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Fri, 3 May 2024 13:37:46 -0400 Subject: [PATCH 154/347] feature: Direct Reservation context manager (#955) * feature: context manager for reservation arns Co-authored-by: Cody Wang --- examples/reservation.py | 17 +- src/braket/aws/__init__.py | 1 + src/braket/aws/aws_session.py | 27 +++ src/braket/aws/direct_reservations.py | 98 ++++++++++ test/integ_tests/test_reservation_arn.py | 24 +-- .../braket/aws/test_direct_reservations.py | 181 ++++++++++++++++++ 6 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 src/braket/aws/direct_reservations.py create mode 100644 test/unit_tests/braket/aws/test_direct_reservations.py diff --git a/examples/reservation.py b/examples/reservation.py index 682f71f50..83be87ebd 100644 --- a/examples/reservation.py +++ b/examples/reservation.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.aws import AwsDevice +from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices @@ -19,6 +19,17 @@ device = AwsDevice(Devices.IonQ.Aria1) # To run a task in a device reservation, change the device to the one you reserved -# and fill in your reservation ARN -task = device.run(bell, shots=100, reservation_arn="reservation ARN") +# and fill in your reservation ARN. +with DirectReservation(device, reservation_arn=""): + task = device.run(bell, shots=100) +print(task.result().measurement_counts) + +# Alternatively, you may start the reservation globally +reservation = DirectReservation(device, reservation_arn="").start() +task = device.run(bell, shots=100) +print(task.result().measurement_counts) +reservation.stop() # stop creating tasks in the reservation + +# Lastly, you may pass the reservation ARN directly to a quantum task +task = device.run(bell, shots=100, reservation_arn="") print(task.result().measurement_counts) diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index d0b3a3411..3be348f34 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -16,3 +16,4 @@ from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch # noqa: F401 from braket.aws.aws_session import AwsSession # noqa: F401 +from braket.aws.direct_reservations import DirectReservation # noqa: F401 diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 16a021e7d..b4cdfcd31 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -17,6 +17,7 @@ import os import os.path import re +import warnings from functools import cache from pathlib import Path from typing import Any, NamedTuple, Optional @@ -235,6 +236,32 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Returns: str: The ARN of the quantum task. """ + # Add reservation arn if available and device is correct. + context_device_arn = os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + context_reservation_arn = os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + # if the task has a reservation_arn and also context does, raise a warning + # Raise warning if reservation ARN is found in both context and task parameters + task_has_reservation = any( + item.get("type") == "RESERVATION_TIME_WINDOW_ARN" + for item in boto3_kwargs.get("associations", []) + ) + if task_has_reservation and context_reservation_arn: + warnings.warn( + "A reservation ARN was passed to 'CreateQuantumTask', but it is being overridden " + "by a 'DirectReservation' context. If this was not intended, please review your " + "reservation ARN settings or the context in which 'CreateQuantumTask' is called." + ) + + # Ensure reservation only applies to specific device + if context_device_arn == boto3_kwargs["deviceArn"] and context_reservation_arn: + boto3_kwargs["associations"] = [ + { + "arn": context_reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + # Add job token to request, if available. job_token = os.getenv("AMZN_BRAKET_JOB_TOKEN") if job_token: diff --git a/src/braket/aws/direct_reservations.py b/src/braket/aws/direct_reservations.py new file mode 100644 index 000000000..4ffcb8fce --- /dev/null +++ b/src/braket/aws/direct_reservations.py @@ -0,0 +1,98 @@ +# 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. + +from __future__ import annotations + +import os +import warnings +from contextlib import AbstractContextManager + +from braket.aws.aws_device import AwsDevice +from braket.devices import Device + + +class DirectReservation(AbstractContextManager): + """ + Context manager that modifies AwsQuantumTasks created within the context to use a reservation + ARN for all tasks targeting the specified device. Note: this context manager only allows for + one reservation at a time. + + Reservations are AWS account and device specific. Only the AWS account that created the + reservation can use your reservation ARN. Additionally, the reservation ARN is only valid on the + reserved device at the chosen start and end times. + + Args: + device (Device | str | None): The Braket device for which you have a reservation ARN, or + optionally the device ARN. + reservation_arn (str | None): The Braket Direct reservation ARN to be applied to all + quantum tasks run within the context. + + Examples: + As a context manager + >>> with DirectReservation(device_arn, reservation_arn=""): + ... task1 = device.run(circuit, shots) + ... task2 = device.run(circuit, shots) + + or start the reservation + >>> DirectReservation(device_arn, reservation_arn="").start() + ... task1 = device.run(circuit, shots) + ... task2 = device.run(circuit, shots) + + References: + + [1] https://docs.aws.amazon.com/braket/latest/developerguide/braket-reservations.html + """ + + _is_active = False # Class variable to track active reservation context + + def __init__(self, device: Device | str | None, reservation_arn: str | None): + if isinstance(device, AwsDevice): + self.device_arn = device.arn + elif isinstance(device, str): + self.device_arn = AwsDevice(device).arn # validate ARN early + elif isinstance(device, Device) or device is None: # LocalSimulator + warnings.warn( + "Using a local simulator with the reservation. For a reservation on a QPU, please " + "ensure the device matches the reserved Braket device." + ) + self.device_arn = "" # instead of None, use empty string + else: + raise TypeError("Device must be an AwsDevice or its ARN, or a local simulator device.") + + self.reservation_arn = reservation_arn + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.stop() + + def start(self) -> None: + """Start the reservation context.""" + if DirectReservation._is_active: + raise RuntimeError("Another reservation is already active.") + + os.environ["AMZN_BRAKET_RESERVATION_DEVICE_ARN"] = self.device_arn + if self.reservation_arn: + os.environ["AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN"] = self.reservation_arn + DirectReservation._is_active = True + + def stop(self) -> None: + """Stop the reservation context.""" + if not DirectReservation._is_active: + warnings.warn("Reservation context is not active.") + return + os.environ.pop("AMZN_BRAKET_RESERVATION_DEVICE_ARN", None) + os.environ.pop("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN", None) + DirectReservation._is_active = False diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index 98b87f075..f5efd7227 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -15,12 +15,12 @@ import pytest from botocore.exceptions import ClientError -from test_create_quantum_job import decorator_python_version -from braket.aws import AwsDevice +from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job +from braket.test.integ_tests.test_create_quantum_job import decorator_python_version @pytest.fixture @@ -36,11 +36,11 @@ def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): device = AwsDevice(Devices.IonQ.Harmony) with pytest.raises(ClientError, match="Reservation arn is invalid"): - device.run( - circuit, - shots=10, - reservation_arn=reservation_arn, - ) + device.run(circuit, shots=10, reservation_arn=reservation_arn) + + with pytest.raises(ClientError, match="Reservation arn is invalid"): + with DirectReservation(device, reservation_arn=reservation_arn): + device.run(circuit, shots=10) def test_create_task_via_reservation_arn_on_simulator(reservation_arn): @@ -48,11 +48,11 @@ def test_create_task_via_reservation_arn_on_simulator(reservation_arn): device = AwsDevice(Devices.Amazon.SV1) with pytest.raises(ClientError, match="Braket Direct is not supported for"): - device.run( - circuit, - shots=10, - reservation_arn=reservation_arn, - ) + device.run(circuit, shots=10, reservation_arn=reservation_arn) + + with pytest.raises(ClientError, match="Braket Direct is not supported for"): + with DirectReservation(device, reservation_arn=reservation_arn): + device.run(circuit, shots=10) @pytest.mark.xfail( diff --git a/test/unit_tests/braket/aws/test_direct_reservations.py b/test/unit_tests/braket/aws/test_direct_reservations.py new file mode 100644 index 000000000..332421e7a --- /dev/null +++ b/test/unit_tests/braket/aws/test_direct_reservations.py @@ -0,0 +1,181 @@ +# 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. + +import os +from unittest.mock import MagicMock, patch + +import pytest + +from braket.aws import AwsDevice, AwsSession, DirectReservation +from braket.devices import LocalSimulator + +RESERVATION_ARN = "arn:aws:braket:us-east-1:123456789:reservation/uuid" +DEVICE_ARN = "arn:aws:braket:us-east-1:123456789:device/qpu/ionq/Forte-1" +VALUE_ERROR_MESSAGE = "Device must be an AwsDevice or its ARN, or a local simulator device." +RUNTIME_ERROR_MESSAGE = "Another reservation is already active." + + +@pytest.fixture +def aws_device(): + mock_device = MagicMock(spec=AwsDevice) + mock_device._arn = DEVICE_ARN + type(mock_device).arn = property(lambda x: DEVICE_ARN) + return mock_device + + +def test_direct_reservation_aws_device(aws_device): + with DirectReservation(aws_device, RESERVATION_ARN) as reservation: + assert reservation.device_arn == DEVICE_ARN + assert reservation.reservation_arn == RESERVATION_ARN + assert reservation._is_active + + +def test_direct_reservation_device_str(aws_device): + with patch( + "braket.aws.AwsDevice.__init__", + side_effect=lambda self, *args, **kwargs: setattr(self, "_arn", DEVICE_ARN), + autospec=True, + ): + with patch("braket.aws.AwsDevice", return_value=aws_device, autospec=True): + with DirectReservation(DEVICE_ARN, RESERVATION_ARN) as reservation: + assert reservation.device_arn == DEVICE_ARN + assert reservation.reservation_arn == RESERVATION_ARN + assert reservation._is_active + + +def test_direct_reservation_local_simulator(): + mock_device = MagicMock(spec=LocalSimulator) + with pytest.warns(UserWarning): + with DirectReservation(mock_device, RESERVATION_ARN) as reservation: + assert os.environ["AMZN_BRAKET_RESERVATION_DEVICE_ARN"] == "" + assert os.environ["AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN"] == RESERVATION_ARN + assert reservation._is_active is True + + +@pytest.mark.parametrize("device", [123, False, [aws_device], {"a": 1}]) +def test_direct_reservation_invalid_inputs(device): + with pytest.raises(TypeError): + DirectReservation(device, RESERVATION_ARN) + + +def test_direct_reservation_local_no_reservation(): + mock_device = MagicMock(spec=LocalSimulator) + mock_device.create_quantum_task = MagicMock() + kwargs = { + "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + "shots": 1, + } + with DirectReservation(mock_device, None): + mock_device.create_quantum_task(**kwargs) + mock_device.create_quantum_task.assert_called_once_with(**kwargs) + + +def test_context_management(aws_device): + with DirectReservation(aws_device, RESERVATION_ARN): + assert os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") == DEVICE_ARN + assert os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") == RESERVATION_ARN + assert not os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + assert not os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + +def test_start_reservation_already_active(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + reservation.start() + with pytest.raises(RuntimeError, match=RUNTIME_ERROR_MESSAGE): + reservation.start() + reservation.stop() + + +def test_stop_reservation_not_active(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + with pytest.warns(UserWarning): + reservation.stop() + + +def test_multiple_start_stop_cycles(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + reservation.start() + reservation.stop() + reservation.start() + reservation.stop() + assert not os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + assert not os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + +def test_two_direct_reservations(aws_device): + with pytest.raises(RuntimeError, match=RUNTIME_ERROR_MESSAGE): + with DirectReservation(aws_device, RESERVATION_ARN): + with DirectReservation(aws_device, "reservation_arn_example_2"): + pass + + +def test_create_quantum_task_with_correct_device_and_reservation(aws_device): + kwargs = {"deviceArn": DEVICE_ARN, "shots": 1} + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + with DirectReservation(aws_device, RESERVATION_ARN): + aws_session.create_quantum_task(**kwargs) + kwargs["associations"] = [ + { + "arn": RESERVATION_ARN, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + mock_client.create_quantum_task.assert_called_once_with(**kwargs) + + +def test_warning_for_overridden_reservation_arn(aws_device): + kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [ + { + "arn": "task_reservation_arn", + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ], + } + correct_kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [ + { + "arn": RESERVATION_ARN, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ], + } + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + with pytest.warns( + UserWarning, + match="A reservation ARN was passed to 'CreateQuantumTask', but it is being overridden", + ): + with DirectReservation(aws_device, RESERVATION_ARN): + aws_session.create_quantum_task(**kwargs) + mock_client.create_quantum_task.assert_called_once_with(**correct_kwargs) + + +def test_warning_not_triggered_wrong_association_type(): + kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [{"type": "OTHER_TYPE"}], + } + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + aws_session.create_quantum_task(**kwargs) + mock_client.create_quantum_task.assert_called_once_with(**kwargs) From 84c3bb7c7cecefbe0676c92c1748aa729bdfc6cf Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 3 May 2024 14:44:14 -0700 Subject: [PATCH 155/347] doc: correct the example in the measure docstring (#965) --- src/braket/circuits/circuit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 94d53248c..f15e03647 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -737,7 +737,6 @@ def measure(self, target_qubits: QubitSetInput) -> Circuit: [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] """ if not isinstance(target_qubits, Iterable): From 2730aa1405695d389e747815a20ef2ee37d39389 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 6 May 2024 14:23:45 -0700 Subject: [PATCH 156/347] infra: add opts for tox builds ran in parallel (#759) --- .github/workflows/check-format.yml | 2 +- README.md | 6 ++++++ tox.ini | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 8f6807b5e..a6106b2d7 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -26,4 +26,4 @@ jobs: pip install tox - name: Run code format checks run: | - tox -e linters_check + tox -e linters_check -p auto diff --git a/README.md b/README.md index b03bd1ef4..0c935853d 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,12 @@ To run linters and doc generators and unit tests: tox ``` +or if your machine can handle multithreaded workloads, run them in parallel with: + +```bash +tox -p auto +``` + ### Integration Tests First, configure a profile to use your account to interact with AWS. To learn more, see [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). diff --git a/tox.ini b/tox.ini index 98a9b30e3..d4fae64c5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] envlist = clean,linters,docs,unit-tests - [testenv] parallel_show_output = true package = wheel @@ -111,7 +110,7 @@ deps = sphinx-rtd-theme sphinxcontrib-apidoc commands = - sphinx-build -E -T -b html doc build/documentation/html + sphinx-build -E -T -b html doc build/documentation/html -j auto [testenv:serve-docs] basepython = python3 From 7824bfbb4ed7215143d578f1754bc5881458cf19 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 6 May 2024 15:04:00 -0700 Subject: [PATCH 157/347] infra: allow worksteal for testing (#960) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d75c4f034..ab93f5955 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ test=pytest xfail_strict = true # https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html addopts = - --verbose -n logical --durations=0 --durations-min=1 + --verbose -n logical --durations=0 --durations-min=1 --dist worksteal testpaths = test/unit_tests filterwarnings= # Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, From 6d30734ace5d5b98884626aba627cd421bb4a647 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 6 May 2024 15:20:42 -0700 Subject: [PATCH 158/347] test: Extract `decorator_python_version` (#968) --- model.tar.gz | Bin 336 -> 0 bytes test/integ_tests/job_testing_utils.py | 25 ++++++++++++++++++++ test/integ_tests/test_create_quantum_job.py | 12 ++-------- test/integ_tests/test_reservation_arn.py | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) delete mode 100644 model.tar.gz create mode 100644 test/integ_tests/job_testing_utils.py diff --git a/model.tar.gz b/model.tar.gz deleted file mode 100644 index 93bf6a4a03f7d08314601e2907a704651eb0b07a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmV-W0k8faiwFP!000001MHQ}OT#c2#(Va!2svx^rcE0sc<_@AJcxpL8(AB)b4^B) z%4F<+H{HjjuuWKXsQq1x%3_`*Q35O_NgDPfvx=n;VBX>FXTDp6uL>oc|;iH ztQ*0R_tGt1vB_fzy6azFJY4nqPd6kr(*HrLP1T3Kf&b071ir@3{KvGOe~7{W?VZW5 zu+G2H+HI@b<^R(B&+yQQH|ZYJS6PVVLx9iF3@cGcKUmphq=$Bp2`9+JzZAK3G8=ep zA>m_$-z!zCY6Zn}FI2{Lo>s{h=3~(^)ykK>$jr~2DW$KH$_tfy0wi27yVa%;u4*+I ii(EN5b$EX0i)v|UY58M(0ssL2{{sNvw;Ch>3;+NyA)$Q$ diff --git a/test/integ_tests/job_testing_utils.py b/test/integ_tests/job_testing_utils.py new file mode 100644 index 000000000..4493df180 --- /dev/null +++ b/test/integ_tests/job_testing_utils.py @@ -0,0 +1,25 @@ +# 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. + +import re + +from braket.aws import AwsSession +from braket.jobs import Framework, retrieve_image + + +def decorator_python_version(): + aws_session = AwsSession() + image_uri = retrieve_image(Framework.BASE, aws_session.region) + tag = aws_session.get_full_image_tag(image_uri) + major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() + return int(major_version), int(minor_version) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index be0e49a85..ce88d122b 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -22,19 +22,11 @@ import job_test_script import pytest from job_test_module.job_test_submodule.job_test_submodule_file import submodule_helper +from job_testing_utils import decorator_python_version -from braket.aws import AwsSession from braket.aws.aws_quantum_job import AwsQuantumJob from braket.devices import Devices -from braket.jobs import Framework, get_input_data_dir, hybrid_job, retrieve_image, save_job_result - - -def decorator_python_version(): - aws_session = AwsSession() - image_uri = retrieve_image(Framework.BASE, aws_session.region) - tag = aws_session.get_full_image_tag(image_uri) - major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() - return int(major_version), int(minor_version) +from braket.jobs import get_input_data_dir, hybrid_job, save_job_result def test_failed_quantum_job(aws_session, capsys, failed_quantum_job): diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index f5efd7227..64135f76e 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -15,12 +15,12 @@ import pytest from botocore.exceptions import ClientError +from job_testing_utils import decorator_python_version from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job -from braket.test.integ_tests.test_create_quantum_job import decorator_python_version @pytest.fixture From 376fb90d122ba88d2b312081705a8d1185903a95 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 6 May 2024 22:40:25 +0000 Subject: [PATCH 159/347] prepare release v1.79.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cb8ae8a6..49eba0f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.79.0 (2024-05-06) + +### Features + + * Direct Reservation context manager + +### Documentation Changes + + * correct the example in the measure docstring + ## v1.78.0 (2024-04-18) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fec1b4f08..1d9dd72b1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.78.1.dev0" +__version__ = "1.79.0" From b791858ca6a5e036d727a9fb4e0f377b0b0b4bff Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 6 May 2024 22:40:25 +0000 Subject: [PATCH 160/347] update development version to v1.79.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1d9dd72b1..1f46f945a 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.0" +__version__ = "1.79.1.dev0" From 6c4282e7eafcff906c4cfc6804bf06209d82a4fc Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 7 May 2024 18:09:55 -0700 Subject: [PATCH 161/347] fix: check the qubit set length against observables (#970) --- src/braket/circuits/result_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index b66d4da67..3e9f0dfad 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -221,7 +221,7 @@ def __init__( "target length is equal to the observable term's qubits count." ) self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(target, observable.summands): + for term_target, obs in zip(self._target, observable.summands): if obs.qubit_count != len(term_target): raise ValueError( "Sum observable's target shape must be a nested list where each term's " From dfd75b38aba98ba748ef7c99d8e241f527760c39 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 May 2024 22:01:49 +0000 Subject: [PATCH 162/347] prepare release v1.79.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49eba0f5b..e54b3f658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.79.1 (2024-05-08) + +### Bug Fixes and Other Changes + + * check the qubit set length against observables + ## v1.79.0 (2024-05-06) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1f46f945a..c15ebeb09 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.1.dev0" +__version__ = "1.79.1" From ec5edafd43dbb498bf5e0dc623f6b1ff74b4698c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 May 2024 22:01:49 +0000 Subject: [PATCH 163/347] update development version to v1.79.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c15ebeb09..0215a4833 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.1" +__version__ = "1.79.2.dev0" From 7ba54cedaa300f55dd6031a47f66d3fce7eec17f Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 21 May 2024 12:54:59 -0400 Subject: [PATCH 164/347] feature: Add support for SerializableProgram abstraction to Device interface (#976) --- src/braket/aws/aws_quantum_task.py | 29 +++++++++ src/braket/circuits/serialization.py | 21 +++++++ src/braket/devices/local_simulator.py | 60 +++++++++++++++---- .../braket/aws/test_aws_quantum_task.py | 28 +++++++++ .../braket/devices/test_local_simulator.py | 38 ++++++++++++ 5 files changed, 164 insertions(+), 12 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 17ae29252..a21c7782c 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -34,6 +34,7 @@ IRType, OpenQASMSerializationProperties, QubitReferenceType, + SerializableProgram, ) from braket.device_schema import GateModelParameters from braket.device_schema.dwave import ( @@ -623,6 +624,34 @@ def _( return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) +@_create_internal.register +def _( + serializable_program: SerializableProgram, + aws_session: AwsSession, + create_task_kwargs: dict[str, Any], + device_arn: str, + device_parameters: Union[dict, BraketSchemaBase], + _disable_qubit_rewiring: bool, + inputs: dict[str, float], + gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + *args, + **kwargs, +) -> AwsQuantumTask: + openqasm_program = OpenQASMProgram(source=serializable_program.to_ir(ir_type=IRType.OPENQASM)) + return _create_internal( + openqasm_program, + aws_session, + create_task_kwargs, + device_arn, + device_parameters, + _disable_qubit_rewiring, + inputs, + gate_definitions, + *args, + **kwargs, + ) + + @_create_internal.register def _( blackbird_program: BlackbirdProgram, diff --git a/src/braket/circuits/serialization.py b/src/braket/circuits/serialization.py index afcb5d118..fdee7d144 100644 --- a/src/braket/circuits/serialization.py +++ b/src/braket/circuits/serialization.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from abc import ABC, abstractmethod from dataclasses import dataclass from enum import Enum @@ -32,6 +33,26 @@ class QubitReferenceType(str, Enum): PHYSICAL = "PHYSICAL" +class SerializableProgram(ABC): + @abstractmethod + def to_ir( + self, + ir_type: IRType = IRType.OPENQASM, + ) -> str: + """Serializes the program into an intermediate representation. + + Args: + ir_type (IRType): The IRType to use for converting the program to its + IR representation. Defaults to IRType.OPENQASM. + + Raises: + ValueError: Raised if the supplied `ir_type` is not supported. + + Returns: + str: A representation of the program in the `ir_type` format. + """ + + @dataclass class OpenQASMSerializationProperties: """Properties for serializing a circuit to OpenQASM. diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 1dec56d37..15ec904de 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -25,11 +25,11 @@ from braket.circuits import Circuit from braket.circuits.circuit_helpers import validate_circuit_and_shots from braket.circuits.noise_model import NoiseModel -from braket.circuits.serialization import IRType +from braket.circuits.serialization import IRType, SerializableProgram from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.devices.device import Device from braket.ir.ahs import Program as AHSProgram -from braket.ir.openqasm import Program +from braket.ir.openqasm import Program as OpenQASMProgram from braket.simulator import BraketSimulator from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( @@ -80,7 +80,9 @@ def __init__( def run( self, - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + task_specification: Union[ + Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram + ], shots: int = 0, inputs: Optional[dict[str, float]] = None, *args: Any, @@ -89,7 +91,7 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]): + task_specification (Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]): # noqa E501 The quantum task specification. shots (int): The number of times to run the circuit or annealing problem. Default is 0, which means that the simulator will compute the exact @@ -122,8 +124,18 @@ def run( def run_batch( # noqa: C901 self, task_specifications: Union[ - Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], - list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]], + Union[ + Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram + ], + list[ + Union[ + Circuit, + Problem, + OpenQASMProgram, + AnalogHamiltonianSimulation, + SerializableProgram, + ] + ], ], shots: Optional[int] = 0, max_parallel: Optional[int] = None, @@ -134,7 +146,7 @@ def run_batch( # noqa: C901 """Executes a batch of quantum tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], list[Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]]]): + task_specifications (Union[Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram], list[Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]]]): # noqa Single instance or list of quantum task specification. shots (Optional[int]): The number of times to run the quantum task. Default: 0. @@ -163,7 +175,7 @@ def run_batch( # noqa: C901 single_task = isinstance( task_specifications, - (Circuit, Program, Problem, AnalogHamiltonianSimulation), + (Circuit, OpenQASMProgram, Problem, AnalogHamiltonianSimulation), ) single_input = isinstance(inputs, dict) @@ -220,7 +232,9 @@ def registered_backends() -> set[str]: def _run_internal_wrap( self, - task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation], + task_specification: Union[ + Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram + ], shots: Optional[int] = None, inputs: Optional[dict[str, float]] = None, *args, @@ -250,7 +264,12 @@ def _(self, backend_impl: BraketSimulator): def _run_internal( self, task_specification: Union[ - Circuit, Problem, Program, AnalogHamiltonianSimulation, AHSProgram + Circuit, + Problem, + OpenQASMProgram, + AnalogHamiltonianSimulation, + AHSProgram, + SerializableProgram, ], shots: Optional[int] = None, *args, @@ -296,7 +315,7 @@ def _(self, problem: Problem, shots: Optional[int] = None, *args, **kwargs): @_run_internal.register def _( self, - program: Program, + program: OpenQASMProgram, shots: Optional[int] = None, inputs: Optional[dict[str, float]] = None, *args, @@ -308,13 +327,30 @@ def _( if inputs: inputs_copy = program.inputs.copy() if program.inputs is not None else {} inputs_copy.update(inputs) - program = Program( + program = OpenQASMProgram( source=program.source, inputs=inputs_copy, ) + results = simulator.run(program, shots, *args, **kwargs) + + if isinstance(results, GateModelQuantumTaskResult): + return results + return GateModelQuantumTaskResult.from_object(results) + @_run_internal.register + def _( + self, + program: SerializableProgram, + shots: Optional[int] = None, + inputs: Optional[dict[str, float]] = None, + *args, + **kwargs, + ): + program = OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM)) + return self._run_internal(program, shots, inputs, *args, **kwargs) + @_run_internal.register def _( self, diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 16a72da7a..28032d943 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -33,6 +33,7 @@ IRType, OpenQASMSerializationProperties, QubitReferenceType, + SerializableProgram, ) from braket.device_schema import GateModelParameters, error_mitigation from braket.device_schema.dwave import ( @@ -123,6 +124,19 @@ def openqasm_program(): return OpenQASMProgram(source="OPENQASM 3.0; h $0;") +class DummySerializableProgram(SerializableProgram): + def __init__(self, source: str): + self.source = source + + def to_ir(self, ir_type: IRType = IRType.OPENQASM) -> str: + return self.source + + +@pytest.fixture +def serializable_program(): + return DummySerializableProgram(source="OPENQASM 3.0; h $0;") + + @pytest.fixture def blackbird_program(): return BlackbirdProgram(source="Vac | q[0]") @@ -614,6 +628,20 @@ def test_create_openqasm_program_em_serialized(aws_session, arn, openqasm_progra ) +def test_create_serializable_program(aws_session, arn, serializable_program): + aws_session.create_quantum_task.return_value = arn + shots = 21 + AwsQuantumTask.create(aws_session, SIMULATOR_ARN, serializable_program, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + SIMULATOR_ARN, + OpenQASMProgram(source=serializable_program.to_ir()).json(), + S3_TARGET, + shots, + ) + + def test_create_blackbird_program(aws_session, arn, blackbird_program): aws_session.create_quantum_task.return_value = arn shots = 21 diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index a7e8bfe17..216d161c7 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -27,6 +27,7 @@ from braket.annealing import Problem, ProblemType from braket.circuits import Circuit, FreeParameter, Gate, Noise from braket.circuits.noise_model import GateCriteria, NoiseModel, NoiseModelInstruction +from braket.circuits.serialization import IRType, SerializableProgram from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.device_schema.openqasm_device_action_properties import OpenQASMDeviceActionProperties from braket.devices import LocalSimulator, local_simulator @@ -250,6 +251,24 @@ def properties(self) -> DeviceCapabilities: return device_properties +class DummySerializableProgram(SerializableProgram): + def __init__(self, source: str): + self.source = source + + def to_ir(self, ir_type: IRType = IRType.OPENQASM) -> str: + return self.source + + +class DummySerializableProgramSimulator(DummyProgramSimulator): + def run( + self, + program: SerializableProgram, + shots: int = 0, + batch_size: int = 1, + ) -> GateModelQuantumTaskResult: + return GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + class DummyProgramDensityMatrixSimulator(BraketSimulator): def run( self, program: ir.openqasm.Program, shots: Optional[int], *args, **kwargs @@ -556,6 +575,25 @@ def test_run_program_model(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_run_serializable_program_model(): + dummy = DummySerializableProgramSimulator() + sim = LocalSimulator(dummy) + task = sim.run( + DummySerializableProgram( + source=""" +qubit[2] q; +bit[2] c; + +h q[0]; +cnot q[0], q[1]; + +c = measure q; +""" + ) + ) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + @pytest.mark.xfail(raises=ValueError) def test_run_gate_model_value_error(): dummy = DummyCircuitSimulator() From 035409addb9a3f7bc15626b99dec2e22519bbfac Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 22 May 2024 09:11:37 -0700 Subject: [PATCH 165/347] feat: add support for the ARN region (#977) Co-authored-by: Coull Co-authored-by: Tim (Yi-Ting) --- src/braket/aws/aws_device.py | 2 +- src/braket/devices/devices.py | 4 ++ src/braket/jobs/image_uri_config/base.json | 3 +- .../jobs/image_uri_config/pl_pytorch.json | 3 +- .../jobs/image_uri_config/pl_tensorflow.json | 3 +- test/integ_tests/conftest.py | 55 ++++++++++++------- test/integ_tests/test_cost_tracking.py | 37 +++++++------ test/unit_tests/braket/aws/test_aws_device.py | 24 +++++++- tox.ini | 1 + 9 files changed, 89 insertions(+), 43 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 70fc3c078..041098f5a 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -62,7 +62,7 @@ class AwsDevice(Device): device. """ - REGIONS = ("us-east-1", "us-west-1", "us-west-2", "eu-west-2") + REGIONS = ("us-east-1", "us-west-1", "us-west-2", "eu-west-2", "eu-north-1") DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index f4fe8ff8d..fa2c6d025 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -27,6 +27,9 @@ class _DWave(str, Enum): _Advantage6 = "arn:aws:braket:us-west-2::device/qpu/d-wave/Advantage_system6" _DW2000Q6 = "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6" + class _IQM(str, Enum): + Garnet = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" + class _IonQ(str, Enum): Harmony = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" Aria1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" @@ -54,6 +57,7 @@ class _Xanadu(str, Enum): Amazon = _Amazon # DWave = _DWave IonQ = _IonQ + IQM = _IQM OQC = _OQC QuEra = _QuEra Rigetti = _Rigetti diff --git a/src/braket/jobs/image_uri_config/base.json b/src/braket/jobs/image_uri_config/base.json index eb71e60fd..c7aef2be2 100644 --- a/src/braket/jobs/image_uri_config/base.json +++ b/src/braket/jobs/image_uri_config/base.json @@ -5,6 +5,7 @@ "us-east-1", "us-west-1", "us-west-2", - "eu-west-2" + "eu-west-2", + "eu-north-1" ] } diff --git a/src/braket/jobs/image_uri_config/pl_pytorch.json b/src/braket/jobs/image_uri_config/pl_pytorch.json index c7e28fbde..0a00e8537 100644 --- a/src/braket/jobs/image_uri_config/pl_pytorch.json +++ b/src/braket/jobs/image_uri_config/pl_pytorch.json @@ -5,6 +5,7 @@ "us-east-1", "us-west-1", "us-west-2", - "eu-west-2" + "eu-west-2", + "eu-north-1" ] } diff --git a/src/braket/jobs/image_uri_config/pl_tensorflow.json b/src/braket/jobs/image_uri_config/pl_tensorflow.json index 3278a8712..c43792e8a 100644 --- a/src/braket/jobs/image_uri_config/pl_tensorflow.json +++ b/src/braket/jobs/image_uri_config/pl_tensorflow.json @@ -5,6 +5,7 @@ "us-east-1", "us-west-1", "us-west-2", - "eu-west-2" + "eu-west-2", + "eu-north-1" ] } diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index b8d0d43df..80a8f5c8f 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -38,6 +38,7 @@ def pytest_configure_node(node): node.workerinput["JOB_FAILED_NAME"] = job_fail_name if endpoint := os.getenv("BRAKET_ENDPOINT"): node.workerinput["BRAKET_ENDPOINT"] = endpoint + node.workerinput["AWS_REGION"] = os.getenv("AWS_REGION") def pytest_xdist_node_collection_finished(ids): @@ -48,8 +49,11 @@ def pytest_xdist_node_collection_finished(ids): """ run_jobs = any("job" in test for test in ids) profile_name = os.environ["AWS_PROFILE"] - aws_session = AwsSession(boto3.session.Session(profile_name=profile_name)) - if run_jobs and os.getenv("JOBS_STARTED") is None: + region_name = os.getenv("AWS_REGION") + aws_session = AwsSession( + boto3.session.Session(profile_name=profile_name, region_name=region_name) + ) + if run_jobs and os.getenv("JOBS_STARTED") is None and region_name != "eu-north-1": AwsQuantumJob.create( "arn:aws:braket:::device/quantum-simulator/amazon/sv1", job_name=job_fail_name, @@ -72,9 +76,10 @@ def pytest_xdist_node_collection_finished(ids): @pytest.fixture(scope="session") -def boto_session(): +def boto_session(request): profile_name = os.environ["AWS_PROFILE"] - return boto3.session.Session(profile_name=profile_name) + region_name = request.config.workerinput["AWS_REGION"] + return boto3.session.Session(profile_name=profile_name, region_name=region_name) @pytest.fixture(scope="session") @@ -137,9 +142,11 @@ def s3_destination_folder(s3_bucket, s3_prefix): @pytest.fixture(scope="session") def braket_simulators(aws_session): - return { - simulator_arn: AwsDevice(simulator_arn, aws_session) for simulator_arn in SIMULATOR_ARNS - } + return ( + {simulator_arn: AwsDevice(simulator_arn, aws_session) for simulator_arn in SIMULATOR_ARNS} + if aws_session.region != "eu-north-1" + else None + ) @pytest.fixture(scope="session") @@ -164,21 +171,29 @@ def job_failed_name(request): @pytest.fixture(scope="session", autouse=True) def completed_quantum_job(job_completed_name): - job_arn = [ - job["jobArn"] - for job in boto3.client("braket").search_jobs(filters=[])["jobs"] - if job["jobName"] == job_completed_name - ][0] + job_arn = ( + [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_completed_name + ][0] + if os.getenv("JOBS_STARTED") + else None + ) - return AwsQuantumJob(arn=job_arn) + return AwsQuantumJob(arn=job_arn) if os.getenv("JOBS_STARTED") else None @pytest.fixture(scope="session", autouse=True) def failed_quantum_job(job_failed_name): - job_arn = [ - job["jobArn"] - for job in boto3.client("braket").search_jobs(filters=[])["jobs"] - if job["jobName"] == job_failed_name - ][0] - - return AwsQuantumJob(arn=job_arn) + job_arn = ( + [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_failed_name + ][0] + if os.getenv("JOBS_STARTED") + else None + ) + + return AwsQuantumJob(arn=job_arn) if os.getenv("JOBS_STARTED") else None diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index d638f80b0..0c06f297a 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -18,7 +18,7 @@ import pytest from botocore.exceptions import ClientError -from braket.aws import AwsDevice, AwsSession +from braket.aws import AwsDevice, AwsDeviceType, AwsSession from braket.circuits import Circuit from braket.tracking import Tracker from braket.tracking.tracker import MIN_SIMULATOR_DURATION @@ -93,23 +93,26 @@ def test_all_devices_price_search(): s = AwsSession(boto3.Session(region_name=region)) # Skip devices with empty execution windows for device in [device for device in devices if device.properties.service.executionWindows]: - try: - s.get_device(device.arn) - - # If we are here, device can create tasks in region - details = { - "shots": 100, - "device": device.arn, - "billed_duration": MIN_SIMULATOR_DURATION, - "job_task": False, - "status": "COMPLETED", - } - tasks[f"task:for:{device.name}:{region}"] = details.copy() - details["job_task"] = True - tasks[f"jobtask:for:{device.name}:{region}"] = details - except s.braket_client.exceptions.ResourceNotFoundException: - # device does not exist in region, so nothing to test + if region == "eu-north-1" and device.type == AwsDeviceType.SIMULATOR: pass + else: + try: + s.get_device(device.arn) + + # If we are here, device can create tasks in region + details = { + "shots": 100, + "device": device.arn, + "billed_duration": MIN_SIMULATOR_DURATION, + "job_task": False, + "status": "COMPLETED", + } + tasks[f"task:for:{device.name}:{region}"] = details.copy() + details["job_task"] = True + tasks[f"jobtask:for:{device.name}:{region}"] = details + except s.braket_client.exceptions.ResourceNotFoundException: + # device does not exist in region, so nothing to test + pass t = Tracker() t._resources = tasks diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 7f9e6179d..a778dfb5f 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1753,6 +1753,16 @@ def test_get_devices(mock_copy_session, aws_session): "providerName": "OQC", } ], + # eu-north-1 + [ + { + "deviceArn": SV1_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + }, + ], # Only two regions to search outside of current ValueError("should not be reachable"), ] @@ -1763,7 +1773,7 @@ def test_get_devices(mock_copy_session, aws_session): ValueError("should not be reachable"), ] mock_copy_session.return_value = session_for_region - # Search order: us-east-1, us-west-1, us-west-2, eu-west-2 + # Search order: us-east-1, us-west-1, us-west-2, eu-west-2, eu-north-1 results = AwsDevice.get_devices( arns=[SV1_ARN, DWAVE_ARN, IONQ_ARN, OQC_ARN], provider_names=["Amazon Braket", "D-Wave", "IonQ", "OQC"], @@ -1858,6 +1868,16 @@ def test_get_devices_with_error_in_region(mock_copy_session, aws_session): "providerName": "OQC", } ], + # eu-north-1 + [ + { + "deviceArn": SV1_ARN, + "deviceName": "SV1", + "deviceType": "SIMULATOR", + "deviceStatus": "ONLINE", + "providerName": "Amazon Braket", + }, + ], # Only two regions to search outside of current ValueError("should not be reachable"), ] @@ -1867,7 +1887,7 @@ def test_get_devices_with_error_in_region(mock_copy_session, aws_session): ValueError("should not be reachable"), ] mock_copy_session.return_value = session_for_region - # Search order: us-east-1, us-west-1, us-west-2, eu-west-2 + # Search order: us-east-1, us-west-1, us-west-2, eu-west-2, eu-north-1 results = AwsDevice.get_devices( statuses=["ONLINE"], aws_session=aws_session, diff --git a/tox.ini b/tox.ini index d4fae64c5..95c862106 100644 --- a/tox.ini +++ b/tox.ini @@ -29,6 +29,7 @@ deps = {[test-deps]deps} passenv = AWS_PROFILE + AWS_REGION BRAKET_ENDPOINT commands = pytest test/integ_tests {posargs} From 14385289cab261be7ea9f13ae45ab390c5620ae8 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 22 May 2024 10:19:48 -0700 Subject: [PATCH 166/347] fix: job fixture for endpoint support (#978) Co-authored-by: Coull --- test/integ_tests/conftest.py | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 80a8f5c8f..d187ca3c4 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -171,29 +171,21 @@ def job_failed_name(request): @pytest.fixture(scope="session", autouse=True) def completed_quantum_job(job_completed_name): - job_arn = ( - [ - job["jobArn"] - for job in boto3.client("braket").search_jobs(filters=[])["jobs"] - if job["jobName"] == job_completed_name - ][0] - if os.getenv("JOBS_STARTED") - else None - ) + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_completed_name + ][0] - return AwsQuantumJob(arn=job_arn) if os.getenv("JOBS_STARTED") else None + return AwsQuantumJob(arn=job_arn) @pytest.fixture(scope="session", autouse=True) def failed_quantum_job(job_failed_name): - job_arn = ( - [ - job["jobArn"] - for job in boto3.client("braket").search_jobs(filters=[])["jobs"] - if job["jobName"] == job_failed_name - ][0] - if os.getenv("JOBS_STARTED") - else None - ) + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_failed_name + ][0] - return AwsQuantumJob(arn=job_arn) if os.getenv("JOBS_STARTED") else None + return AwsQuantumJob(arn=job_arn) From 5453cf55f94ce23c8fb67760b335468269e3a4ba Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 22 May 2024 18:12:49 +0000 Subject: [PATCH 167/347] prepare release v1.80.0 --- CHANGELOG.md | 11 +++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e54b3f658..343965f60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.80.0 (2024-05-22) + +### Features + + * add support for the ARN region + * Add support for SerializableProgram abstraction to Device interface + +### Bug Fixes and Other Changes + + * job fixture for endpoint support + ## v1.79.1 (2024-05-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0215a4833..41dc99b01 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.2.dev0" +__version__ = "1.80.0" From 1c46ca7f18f68b963ba06edf7bb719258e838a16 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 22 May 2024 18:12:49 +0000 Subject: [PATCH 168/347] update development version to v1.80.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 41dc99b01..ce2f1eb66 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.80.0" +__version__ = "1.80.1.dev0" From ecbe76d04cdb846fe5e6279a2aa27d7707b38e5e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 3 Jun 2024 10:56:55 -0700 Subject: [PATCH 169/347] infra: remove flake8 constraints (#964) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6763c4a55..5d84aecee 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ "test": [ "black", "botocore", - "flake8<=5.0.4", + "flake8", "isort", "jsonschema==3.2.0", "pre-commit", From d5dfbf40182f1858a2cf5db56962daf2e5e57cc3 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 3 Jun 2024 14:49:03 -0700 Subject: [PATCH 170/347] infra: change action updates to monthly (#951) --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 04595aed1..4f575c3e2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,6 @@ updates: directory: "/" schedule: # Check for updates to GitHub Actions every week - interval: "weekly" + interval: "monthly" commit-message: prefix: infra From 2f021072942ffe901ab17e41e35701d1c7580a0d Mon Sep 17 00:00:00 2001 From: Kazuki Tsuoka Date: Fri, 7 Jun 2024 02:40:33 +0900 Subject: [PATCH 171/347] Implement `braket.ahs.AnalogHamiltonianSimulation.from_ir()` (#983) * feature: implement `braket.ahs.AnalogHamiltonianSimulation.from_ir()` * change: improve tests, remove duplicate codes * change: change variable name in field.py * change: Update field.py * fix: fix coverage --- .../ahs/analog_hamiltonian_simulation.py | 47 +++++ src/braket/ahs/field.py | 27 +++ .../ahs/test_analog_hamiltonian_simulation.py | 166 ++++++++++++++++++ test/unit_tests/braket/ahs/test_field.py | 20 +++ 4 files changed, 260 insertions(+) diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index af02471e2..f11675d41 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -23,6 +23,7 @@ from braket.ahs.hamiltonian import Hamiltonian from braket.ahs.local_detuning import LocalDetuning from braket.device_schema import DeviceActionType +from braket.timings.time_series import TimeSeries class AnalogHamiltonianSimulation: @@ -49,6 +50,52 @@ def hamiltonian(self) -> Hamiltonian: """Hamiltonian: The hamiltonian to simulate.""" return self._hamiltonian + @staticmethod + def from_ir(source: ir.Program) -> AnalogHamiltonianSimulation: + """Converts the canonical intermediate representation into + the Analog Hamiltonian Simulation. + + Args: + source (ir.Program): The IR representation of the circuit. + + Returns: + AnalogHamiltonianSimulation: The Analog Hamiltonian Simulation. + """ + atom_arrangement = AtomArrangement() + for site, fill in zip(source.setup.ahs_register.sites, source.setup.ahs_register.filling): + atom_arrangement.add( + coordinate=site, site_type=SiteType.FILLED if fill == 1 else SiteType.VACANT + ) + hamiltonian = Hamiltonian() + for term in source.hamiltonian.drivingFields: + amplitude = TimeSeries.from_lists( + times=term.amplitude.time_series.times, + values=term.amplitude.time_series.values, + ) + phase = TimeSeries.from_lists( + times=term.phase.time_series.times, + values=term.phase.time_series.values, + ) + detuning = TimeSeries.from_lists( + times=term.detuning.time_series.times, + values=term.detuning.time_series.values, + ) + hamiltonian += DrivingField( + amplitude=amplitude, + phase=phase, + detuning=detuning, + ) + for term in source.hamiltonian.localDetuning: + hamiltonian += LocalDetuning.from_lists( + times=term.magnitude.time_series.times, + values=term.magnitude.time_series.values, + pattern=term.magnitude.pattern, + ) + return AnalogHamiltonianSimulation( + register=atom_arrangement, + hamiltonian=hamiltonian, + ) + def to_ir(self) -> ir.Program: """Converts the Analog Hamiltonian Simulation into the canonical intermediate representation. diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index 1522b9d65..28319cc99 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -66,3 +66,30 @@ def discretize( discretized_pattern = self.pattern.discretize(pattern_resolution) discretized_field = Field(time_series=discretized_time_series, pattern=discretized_pattern) return discretized_field + + @staticmethod + def from_lists(times: list[Decimal], values: list[Decimal], pattern: list[Decimal]) -> Field: + """Builds Field from lists of time points, values and pattern. + + Args: + times (list[Decimal]): The time points of the field + values (list[Decimal]): The values of the field + pattern (list[Decimal]): The pattern of the field + + Raises: + ValueError: If the length of times and values differs. + + Returns: + Field: Field. + """ + if not (len(times) == len(values)): + raise ValueError( + f"The lengths of the lists for times({len(times)}) and values({len(values)})\ + are not equal" + ) + + time_series = TimeSeries.from_lists(times=times, values=values) + + field = Field(time_series=time_series, pattern=Pattern(pattern)) + + return field diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index 83178c120..3af8554a1 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -70,6 +70,140 @@ def local_detuning(): ) +@pytest.fixture +def ir(): + return Program.parse_raw_schema( + """ +{ + "braketSchemaHeader": { + "name": "braket.ir.ahs.program", + "version": "1" + }, + "setup": { + "ahs_register": { + "sites": [ + [ + "0.0", + "0.0" + ], + [ + "0.0", + "0.000003" + ], + [ + "0.0", + "0.000006" + ], + [ + "0.000003", + "0.0" + ], + [ + "0.000003", + "0.000003" + ], + [ + "0.000003", + "0.000003" + ], + [ + "0.000003", + "0.000006" + ] + ], + "filling": [ + 1, + 1, + 1, + 1, + 1, + 0, + 0 + ] + } + }, + "hamiltonian": { + "drivingFields": [ + { + "amplitude": { + "time_series": { + "values": [ + "0.0", + "25132700.0", + "25132700.0", + "0.0" + ], + "times": [ + "0.0", + "3E-7", + "0.0000027", + "0.000003" + ] + }, + "pattern": "uniform" + }, + "phase": { + "time_series": { + "values": [ + "0", + "0" + ], + "times": [ + "0.0", + "0.000003" + ] + }, + "pattern": "uniform" + }, + "detuning": { + "time_series": { + "values": [ + "-125664000.0", + "-125664000.0", + "125664000.0", + "125664000.0" + ], + "times": [ + "0.0", + "3E-7", + "0.0000027", + "0.000003" + ] + }, + "pattern": "uniform" + } + } + ], + "localDetuning": [ + { + "magnitude": { + "time_series": { + "values": [ + "-125664000.0", + "125664000.0" + ], + "times": [ + "0.0", + "0.000003" + ] + }, + "pattern": [ + "0.5", + "1.0", + "0.5", + "0.5", + "0.5", + "0.5" + ] + } + } + ] + } +} +""" + ) + + def test_create(): mock0 = Mock() mock1 = Mock() @@ -95,6 +229,38 @@ def test_to_ir_empty(): assert problem == Program.parse_raw_schema(problem.json()) +def test_from_ir(ir): + problem = AnalogHamiltonianSimulation.from_ir(ir).to_ir() + assert problem == ir + assert problem == Program.parse_raw_schema(problem.json()) + + +def test_from_ir_empty(): + empty_ir = Program.parse_raw_schema( + """ +{ + "braketSchemaHeader": { + "name": "braket.ir.ahs.program", + "version": "1" + }, + "setup": { + "ahs_register": { + "sites": [], + "filling": [] + } + }, + "hamiltonian": { + "drivingFields": [], + "localDetuning": [] + } +} +""" + ) + problem = AnalogHamiltonianSimulation.from_ir(empty_ir).to_ir() + assert problem == empty_ir + assert problem == Program.parse_raw_schema(problem.json()) + + @pytest.mark.xfail(raises=TypeError) def test_to_ir_invalid_hamiltonian(register): hamiltonian = Mock() diff --git a/test/unit_tests/braket/ahs/test_field.py b/test/unit_tests/braket/ahs/test_field.py index 2ff6714ce..21006fa6d 100644 --- a/test/unit_tests/braket/ahs/test_field.py +++ b/test/unit_tests/braket/ahs/test_field.py @@ -98,3 +98,23 @@ def test_uniform_field( ) or expected.pattern.series == actual.pattern.series assert expected.time_series.times() == actual.time_series.times() assert expected.time_series.values() == actual.time_series.values() + + +def test_from_lists(): + times = [0, 0.1, 0.2, 0.3] + values = [0.5, 0.8, 0.9, 1.0] + pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] + + sh_field = Field.from_lists(times, values, pattern) + assert sh_field.time_series.times() == times + assert sh_field.time_series.values() == values + assert sh_field.pattern.series == pattern + + +@pytest.mark.xfail(raises=ValueError) +def test_from_lists_not_eq_length(): + times = [0, 0.1, 0.2] + values = [0.5, 0.8, 0.9, 1.0] + pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] + + Field.from_lists(times, values, pattern) From dfdc50d1e8cec3d40c7de5adab80b063d8885c9d Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:16:45 -0700 Subject: [PATCH 172/347] docs: add stack exchange badge to the readme (#956) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0c935853d..415a18c25 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Build status](https://github.com/amazon-braket/amazon-braket-sdk-python/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/amazon-braket/amazon-braket-sdk-python/actions/workflows/python-package.yml) [![codecov](https://codecov.io/gh/amazon-braket/amazon-braket-sdk-python/branch/main/graph/badge.svg?token=1lsqkZL3Ll)](https://codecov.io/gh/amazon-braket/amazon-braket-sdk-python) [![Documentation Status](https://img.shields.io/readthedocs/amazon-braket-sdk-python.svg?logo=read-the-docs)](https://amazon-braket-sdk-python.readthedocs.io/en/latest/?badge=latest) +[![Stack Overflow](https://img.shields.io/badge/StackExchange-Ask%20questions-blue?logo=stackexchange)](https://quantumcomputing.stackexchange.com/questions/tagged/amazon-braket) The Amazon Braket Python SDK is an open source library that provides a framework that you can use to interact with quantum computing hardware devices through Amazon Braket. From 050c6c335459cfebe8c95d3d918a28e9b6cf73a0 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Jun 2024 16:14:24 +0000 Subject: [PATCH 173/347] prepare release v1.80.1 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 343965f60..8e9e637d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.80.1 (2024-06-10) + +### Bug Fixes and Other Changes + + * docs: add stack exchange badge to the readme + * Implement `braket.ahs.AnalogHamiltonianSimulation.from_ir()` + ## v1.80.0 (2024-05-22) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ce2f1eb66..780aafcfb 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.80.1.dev0" +__version__ = "1.80.1" From 0ad486ff4ce642dd2c17e59a50adb2fde7bf4be5 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Jun 2024 16:14:24 +0000 Subject: [PATCH 174/347] update development version to v1.80.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 780aafcfb..be5a3e749 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.80.1" +__version__ = "1.80.2.dev0" From 5679726e534cee4c3c918dc9580833a2f11285b3 Mon Sep 17 00:00:00 2001 From: Lauren Capelluto Date: Wed, 12 Jun 2024 12:38:11 -0400 Subject: [PATCH 175/347] feature: Add IQM to get compiled program convenience method (#1001) --- .../tasks/gate_model_quantum_task_result.py | 2 ++ .../test_gate_model_quantum_task_result.py | 29 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 81f90ae7b..e8267032f 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -145,6 +145,8 @@ def get_compiled_circuit(self) -> Optional[str]: return metadata.rigettiMetadata.compiledProgram elif metadata.oqcMetadata: return metadata.oqcMetadata.compiledProgram + elif metadata.iqmMetadata: + return metadata.iqmMetadata.compiledProgram else: return None diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 5447bd8b2..99ab7e345 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -26,6 +26,7 @@ ResultTypeValue, TaskMetadata, ) +from braket.task_result.iqm_metadata_v1 import IqmMetadata from braket.task_result.oqc_metadata_v1 import OqcMetadata from braket.task_result.rigetti_metadata_v1 import RigettiMetadata from braket.tasks import GateModelQuantumTaskResult @@ -103,6 +104,22 @@ def additional_metadata_rigetti(quil_program): return AdditionalMetadata(action=program, rigettiMetadata=rigetti_metadata) +@pytest.fixture +def additional_metadata_iqm(): + source = """ + OPENQASM 3.0; + bit[2] b; + h $0; + cnot $0, $7; + b[0] = measure $0; + b[1] = measure $7; + """ + program = openqasm.Program(source=source) + iqm_metadata = IqmMetadata(compiledProgram=source) + + return AdditionalMetadata(action=program, iqmMetadata=iqm_metadata) + + @pytest.fixture def qasm2_program(): return """ @@ -160,6 +177,13 @@ def result_oqc(result_obj_1, additional_metadata_oqc): return result +@pytest.fixture +def result_iqm(result_obj_1, additional_metadata_iqm): + result = GateModelQuantumTaskResult.from_object(result_obj_1) + result.additional_metadata = additional_metadata_iqm + return result + + @pytest.fixture def result_str_1(result_obj_1): return result_obj_1.json() @@ -323,6 +347,11 @@ def test_get_compiled_circuit_oqc(result_oqc, qasm2_program): assert result_oqc.get_compiled_circuit() == qasm2_program +def test_get_compiled_circuit_iqm(result_iqm): + """Test get_compiled_circuit method.""" + assert result_iqm.get_compiled_circuit() == result_iqm.additional_metadata.action.source + + def test_get_compiled_circuit_no_qhp_metadata(result_obj_1): """Test get_compiled_circuit method.""" result = GateModelQuantumTaskResult.from_object(result_obj_1) From feedb6a2d9e34c227cd6332e87515620b78512d8 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 13 Jun 2024 16:14:52 +0000 Subject: [PATCH 176/347] prepare release v1.81.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9e637d6..e14f451d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.81.0 (2024-06-13) + +### Features + + * Add IQM to get compiled program convenience method + ## v1.80.1 (2024-06-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index be5a3e749..923d442e3 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.80.2.dev0" +__version__ = "1.81.0" From bc5642955c67da78a4ab851bce060fb5db28858f Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 13 Jun 2024 16:14:52 +0000 Subject: [PATCH 177/347] update development version to v1.81.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 923d442e3..2fb4b5e38 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.81.0" +__version__ = "1.81.1.dev0" From 63b519c780202a3c8e6c222f9bc3771121381fb6 Mon Sep 17 00:00:00 2001 From: Tarun Kumar Allamsetty <70093909+Tarun-Kumar07@users.noreply.github.com> Date: Mon, 17 Jun 2024 20:13:23 +0530 Subject: [PATCH 178/347] fix: Error when FreeParameters are named QASM types (#999) * fix: Error when FreeParameters are named QASM types * fix linting * replace `FreeParameter("b")` with `FreeParameter("d")` in unittests * change: Align code review changes * change: Align code review changes Add openpulse keywords --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/parametric/free_parameter.py | 73 +++++++++++++++++++ .../circuits/test_ascii_circuit_diagram.py | 8 +- test/unit_tests/braket/circuits/test_gates.py | 8 +- .../unit_tests/braket/circuits/test_noises.py | 4 +- .../circuits/test_unicode_circuit_diagram.py | 8 +- .../braket/parametric/test_free_parameter.py | 18 +++++ .../test_free_parameter_expression.py | 2 +- .../braket/pulse/test_pulse_sequence.py | 22 +++--- 8 files changed, 117 insertions(+), 26 deletions(-) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 1f3a69e72..8f437bbca 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -20,6 +20,69 @@ from braket.parametric.free_parameter_expression import FreeParameterExpression +PREDEFINED_VARIABLE_NAMES = {"b", "q"} + +# The reserved words are picked from below +# https://github.com/openqasm/openqasm/blob/main/source/grammar/qasm3Lexer.g4 +# https://github.com/openqasm/openpulse-python/blob/main/source/grammar/openpulseLexer.g4 +QASM_RESERVED_WORDS = { + "OPENQASM", + "include", + "defcalgrammar", + "def", + "cal", + "defcal", + "gate", + "extern", + "box", + "let", + "break", + "continue", + "if", + "else", + "end", + "return", + "for", + "while", + "in", + "pragma", + "input", + "output", + "const", + "readonly", + "mutable", + "qreg", + "qubit", + "creg", + "bool", + "bit", + "int", + "uint", + "float", + "angle", + "complex", + "array", + "void", + "duration", + "stretch", + "gphase", + "inv", + "pow", + "ctrl", + "negctrl", + "dim", + "durationof", + "delay", + "reset", + "measure", + "barrier", + "true", + "false", + "waveform", + "port", + "frame", +} + class FreeParameter(FreeParameterExpression): """Class 'FreeParameter' @@ -94,6 +157,16 @@ def _set_name(self, name: str) -> None: raise TypeError("FreeParameter names must be strings") if not name[0].isalpha() and name[0] != "_": raise ValueError("FreeParameter names must start with a letter or an underscore") + if name in PREDEFINED_VARIABLE_NAMES: + raise ValueError( + f"FreeParameter names must not be one of the Braket reserved variable names: " + f"{PREDEFINED_VARIABLE_NAMES}." + ) + if name in QASM_RESERVED_WORDS: + raise ValueError( + f"FreeParameter names must not be one of the OpenQASM or OpenPulse keywords: " + f"{QASM_RESERVED_WORDS}." + ) self._name = Symbol(name) def to_dict(self) -> dict: diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index aec875568..b10891830 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -741,19 +741,19 @@ def test_noise_multi_probabilities(): def test_noise_multi_probabilities_with_parameter(): a = FreeParameter("a") - b = FreeParameter("b") c = FreeParameter("c") - circ = Circuit().h(0).x(1).pauli_channel(1, a, b, c) + d = FreeParameter("d") + circ = Circuit().h(0).x(1).pauli_channel(1, a, c, d) expected = ( "T : | 0 |", " ", "q0 : -H-----------", " ", - "q1 : -X-PC(a,b,c)-", + "q1 : -X-PC(a,c,d)-", "", "T : | 0 |", "", - "Unassigned parameters: [a, b, c].", + "Unassigned parameters: [a, c, d].", ) _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 0b9ce52d7..285b530c1 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1069,8 +1069,8 @@ def test_bind_values_pulse_gate(): frame = Frame("user_frame", Port("device_port_x", 1e-9), 1e9) gate = Gate.PulseGate( PulseSequence() - .set_frequency(frame, FreeParameter("a") + FreeParameter("b")) - .delay(frame, FreeParameter("c")), + .set_frequency(frame, FreeParameter("a") + FreeParameter("c")) + .delay(frame, FreeParameter("d")), qubit_count, ) @@ -1084,8 +1084,8 @@ def to_ir(pulse_gate): assert a_bound_ir == "\n".join( [ "cal {", - " set_frequency(user_frame, 3.0 + b);", - " delay[c * 1s] user_frame;", + " set_frequency(user_frame, 3.0 + c);", + " delay[d * 1s] user_frame;", "}", ] ) diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 5fffba43f..3b7f08dd8 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -483,10 +483,10 @@ def test_parameter_binding(parameterized_noise, params, expected_noise): def test_parameterized_noise(): - noise = Noise.PauliChannel(FreeParameter("a"), 0.2, FreeParameter("b")) + noise = Noise.PauliChannel(FreeParameter("a"), 0.2, FreeParameter("d")) assert noise.probX == FreeParameter("a") assert noise.probY == 0.2 - assert noise.probZ == FreeParameter("b") + assert noise.probZ == FreeParameter("d") # Additional Unitary noise tests diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index 268c53a96..efc87ee24 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -853,20 +853,20 @@ def test_noise_multi_probabilities(): def test_noise_multi_probabilities_with_parameter(): a = FreeParameter("a") - b = FreeParameter("b") c = FreeParameter("c") - circ = Circuit().h(0).x(1).pauli_channel(1, a, b, c) + d = FreeParameter("d") + circ = Circuit().h(0).x(1).pauli_channel(1, a, c, d) expected = ( "T : │ 0 │", " ┌───┐ ", "q0 : ─┤ H ├───────────────", " └───┘ ", " ┌───┐ ┌───────────┐ ", - "q1 : ─┤ X ├─┤ PC(a,b,c) ├─", + "q1 : ─┤ X ├─┤ PC(a,c,d) ├─", " └───┘ └───────────┘ ", "T : │ 0 │", "", - "Unassigned parameters: [a, b, c].", + "Unassigned parameters: [a, c, d].", ) _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py index b94c60a97..0b42bfe5a 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -67,3 +67,21 @@ def test_sub_successful(free_parameter): def test_sub_wrong_param(free_parameter): assert free_parameter.subs({"alpha": 1}) == FreeParameter("theta") + + +@pytest.mark.parametrize("param_name", ["for", "qubit"]) +def test_error_raised_when_reserved_keyword_used(param_name): + with pytest.raises( + ValueError, + match="FreeParameter names must not be one of the OpenQASM or OpenPulse keywords: ", + ): + FreeParameter(param_name) + + +@pytest.mark.parametrize("param_name", ["b", "q"]) +def test_error_raised_when_predefined_variable_used(param_name): + with pytest.raises( + ValueError, + match="FreeParameter names must not be one of the Braket reserved variable names: ", + ): + FreeParameter(param_name) diff --git a/test/unit_tests/braket/parametric/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py index 1bf6818bf..51402482f 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -165,7 +165,7 @@ def test_sub_return_expression(): @pytest.mark.parametrize( "param, kwargs, expected_value, expected_type", [ - (FreeParameter("a") + 2 * FreeParameter("b"), {"a": 0.1, "b": 0.3}, 0.7, float), + (FreeParameter("a") + 2 * FreeParameter("d"), {"a": 0.1, "d": 0.3}, 0.7, float), (FreeParameter("x"), {"y": 1}, FreeParameter("x"), FreeParameter), (FreeParameter("y"), {"y": -0.1}, -0.1, float), (2 * FreeParameter("i"), {"i": 1}, 2.0, float), diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index da8e0a8a3..9b6092db1 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -81,7 +81,7 @@ def test_pulse_sequence_with_user_defined_frame(user_defined_frame): def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined_frame_2): - param = FreeParameter("a") + 2 * FreeParameter("b") + param = FreeParameter("a") + 2 * FreeParameter("c") pulse_sequence = ( PulseSequence() .set_frequency(predefined_frame_1, param) @@ -135,14 +135,14 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " sigma_dg * 1s, 0.2, 1, false);", " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " set_frequency(predefined_frame_1, a + 2.0 * b);", - " shift_frequency(predefined_frame_1, a + 2.0 * b);", - " set_phase(predefined_frame_1, a + 2.0 * b);", - " shift_phase(predefined_frame_1, -1.0 * a + -2.0 * b);", - " set_scale(predefined_frame_1, a + 2.0 * b);", + " set_frequency(predefined_frame_1, a + 2.0 * c);", + " shift_frequency(predefined_frame_1, a + 2.0 * c);", + " set_phase(predefined_frame_1, a + 2.0 * c);", + " shift_phase(predefined_frame_1, -1.0 * a + -2.0 * c);", + " set_scale(predefined_frame_1, a + 2.0 * c);", " psb[0] = capture_v0(predefined_frame_1);", - " delay[(a + 2.0 * b) * 1s] predefined_frame_1, predefined_frame_2;", - " delay[(a + 2.0 * b) * 1s] predefined_frame_1;", + " delay[(a + 2.0 * c) * 1s] predefined_frame_1, predefined_frame_2;", + " delay[(a + 2.0 * c) * 1s] predefined_frame_1;", " delay[1.0ms] predefined_frame_1;", " barrier predefined_frame_1, predefined_frame_2;", " play(predefined_frame_1, gauss_wf);", @@ -156,7 +156,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined assert pulse_sequence.to_ir() == expected_str_unbound assert pulse_sequence.parameters == { FreeParameter("a"), - FreeParameter("b"), + FreeParameter("c"), FreeParameter("length_g"), FreeParameter("length_dg"), FreeParameter("sigma_g"), @@ -164,9 +164,9 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined FreeParameter("length_c"), } b_bound = pulse_sequence.make_bound_pulse_sequence( - {"b": 2, "length_g": 1e-3, "length_dg": 3e-3, "sigma_dg": 0.4, "length_c": 4e-3} + {"c": 2, "length_g": 1e-3, "length_dg": 3e-3, "sigma_dg": 0.4, "length_c": 4e-3} ) - b_bound_call = pulse_sequence(b=2, length_g=1e-3, length_dg=3e-3, sigma_dg=0.4, length_c=4e-3) + b_bound_call = pulse_sequence(c=2, length_g=1e-3, length_dg=3e-3, sigma_dg=0.4, length_c=4e-3) expected_str_b_bound = "\n".join( [ "OPENQASM 3.0;", From d85eaa483a770b9812626f7acc24c653edd1bd2c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 17 Jun 2024 16:14:25 +0000 Subject: [PATCH 179/347] prepare release v1.81.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e14f451d7..82e97333d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.81.1 (2024-06-17) + +### Bug Fixes and Other Changes + + * Error when FreeParameters are named QASM types + ## v1.81.0 (2024-06-13) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2fb4b5e38..d343a6398 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.81.1.dev0" +__version__ = "1.81.1" From 90f9395fdaab116607990666cadea75559d0e1ad Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 17 Jun 2024 16:14:25 +0000 Subject: [PATCH 180/347] update development version to v1.81.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d343a6398..50ee98c7c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.81.1" +__version__ = "1.81.2.dev0" From 72b59bd49f1a04622b432b230f5c98e07c9e52de Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 19 Jun 2024 14:19:59 -0700 Subject: [PATCH 181/347] infra: add pr_title_check workflow (#991) --- .github/pr-title-checker-config.json | 17 ++++++++++++++ .github/workflows/pr-title-checker.yml | 31 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 .github/pr-title-checker-config.json create mode 100644 .github/workflows/pr-title-checker.yml diff --git a/.github/pr-title-checker-config.json b/.github/pr-title-checker-config.json new file mode 100644 index 000000000..a1c37ac33 --- /dev/null +++ b/.github/pr-title-checker-config.json @@ -0,0 +1,17 @@ +{ + "LABEL": { + "name": "title needs formatting", + "color": "EEEEEE" + }, + "CHECKS": { + "prefixes": ["fix: ", "feat: ", "test: ", "infra: ", "doc: ", "change: ", "break: ", "breaking: ", "deprecation: ", "feature: ", "depr: ", "documentation: "], + "regexp": "docs\\(v[0-9]\\): ", + "regexpFlags": "i", + "ignoreLabels" : ["dont-check-PRs-with-this-label", "meta"] + }, + "MESSAGES": { + "success": "All OK", + "failure": "Failing CI test", + "notice": "" + } +} diff --git a/.github/workflows/pr-title-checker.yml b/.github/workflows/pr-title-checker.yml new file mode 100644 index 000000000..a20344a77 --- /dev/null +++ b/.github/workflows/pr-title-checker.yml @@ -0,0 +1,31 @@ +name: "PR Title Checker" + +on: + push: + branches: + - main + pull_request: + branches: + - main + - feature/** + pull_request_target: + types: + - opened + - edited + - synchronize + - labeled + - unlabeled + +permissions: + pull-requests: write + +jobs: + check: + runs-on: ubuntu-latest + steps: + - name: "Check PR Title" + uses: thehanimo/pr-title-checker@v1.4.1 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + pass_on_octokit_error: false + configuration_path: .github/pr-title-checker-config.json #(optional. defaults to .github/pr-title-checker-config.json) From 748680574963cf0e0e0e84b019665150b04ddf78 Mon Sep 17 00:00:00 2001 From: Stephen Face <60493521+shpface@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:55:44 -0700 Subject: [PATCH 182/347] infra: unpin numpy version (#1003) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5d84aecee..43d2c52ad 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,7 @@ "cloudpickle==2.2.1", "nest-asyncio", "networkx", - "numpy<2", + "numpy", "openpulse", "openqasm3", "sympy", From 7a6025bcffeb3e43fef6b1d2a9884ed3662c0539 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:39:10 -0700 Subject: [PATCH 183/347] infra: bump codecov/codecov-action from 4.1.0 to 4.2.0 (#941) --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f4ac4e916..dbd4d1faf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 + uses: codecov/codecov-action@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 234ab0a56962ffb1694715672436f68f5750f4ac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 13:44:16 -0700 Subject: [PATCH 184/347] infra: bump codecov/codecov-action from 4.2.0 to 4.5.0 (#1011) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index dbd4d1faf..9d398f23d 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@7afa10ed9b269c561c2336fd862446844e0cbf71 # v4.2.0 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 785d2512535af9606fd66eedd4476984c7b41a8b Mon Sep 17 00:00:00 2001 From: Altanali Date: Wed, 26 Jun 2024 13:53:16 -0700 Subject: [PATCH 185/347] feat: Track classical target indices for measurements (#1008) --- src/braket/circuits/braket_program_context.py | 14 ++++++++------ src/braket/circuits/circuit.py | 9 +++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 4371637d3..558692510 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -11,6 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from collections.abc import Iterable from typing import Optional, Union import numpy as np @@ -161,16 +162,17 @@ def handle_parameter_value( return FreeParameterExpression(evaluated_value) return value - def add_measure(self, target: tuple[int]) -> None: + def add_measure( + self, target: tuple[int], classical_targets: Optional[Iterable[int]] = None + ) -> None: """Add a measure instruction to the circuit Args: target (tuple[int]): the target qubits to be measured. + classical_targets (Optional[Iterable[int]]): the classical registers + to use in the qubit measurement. """ - for index, qubit in enumerate(target): + for iter, qubit in enumerate(target): + index = classical_targets[iter] if classical_targets else iter instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) - if self._circuit._measure_targets: - self._circuit._measure_targets.append(qubit) - else: - self._circuit._measure_targets = [qubit] diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f15e03647..9ce7df7f8 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -504,6 +504,11 @@ def add_instruction( # Check if there is a measure instruction on the circuit self._check_if_qubit_measured(instruction, target, target_mapping) + # Update measure targets if instruction is a measurement + if isinstance(instruction.operator, Measure): + measure_target = target or instruction.target[0] + self._measure_targets = (self._measure_targets or []) + [measure_target] + if not target_mapping and not target: # Nothing has been supplied, add instruction instructions_to_add = [instruction] @@ -710,10 +715,6 @@ def _add_measure(self, target_qubits: QubitSetInput) -> None: target=target, ) ) - if self._measure_targets: - self._measure_targets.append(target) - else: - self._measure_targets = [target] def measure(self, target_qubits: QubitSetInput) -> Circuit: """ From eeb7630e5b876b9cb1c3c7a856b66dc0c2c2e4ec Mon Sep 17 00:00:00 2001 From: Altanali Date: Wed, 26 Jun 2024 16:47:54 -0700 Subject: [PATCH 186/347] change: Add test to check classical indices used in measurement are preserved between Circuit and OpenQASM Translations. (#1013) --- setup.py | 2 +- .../braket/circuits/test_circuit.py | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 43d2c52ad..54987c283 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.21.4", + "amazon-braket-default-simulator>=1.25.0", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 71eecd1f1..897828b27 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -3610,3 +3610,32 @@ def test_circuit_with_global_phase(): "b[0] = measure $0;", ] ) + + +def test_from_ir_round_trip_transformation_with_targeted_measurements(): + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .add_instruction(Instruction(Measure(index=2), 1)) + .add_instruction(Instruction(Measure(index=1), 2)) + .add_instruction(Instruction(Measure(index=0), 0)) + ) + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[2] = measure q[1];", + "b[1] = measure q[2];", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ) + + assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") From 6cd842e081300369583974e54caa6509cf3eb90a Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 27 Jun 2024 00:03:09 +0000 Subject: [PATCH 187/347] prepare release v1.82.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82e97333d..f6716703f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.82.0 (2024-06-27) + +### Features + + * Track classical target indices for measurements + +### Bug Fixes and Other Changes + + * Add test to check classical indices used in measurement are preserved between Circuit and OpenQASM Translations. + ## v1.81.1 (2024-06-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 50ee98c7c..1ce2d2394 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.81.2.dev0" +__version__ = "1.82.0" From 239fc81f1dcb7bc298fceca73babdef12281d969 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 27 Jun 2024 00:03:09 +0000 Subject: [PATCH 188/347] update development version to v1.82.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1ce2d2394..4d28af096 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.82.0" +__version__ = "1.82.1.dev0" From 12d0d670bd80427c1b633461cef3bc49c3f2f152 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:33:35 -0700 Subject: [PATCH 189/347] infra: bump actions/setup-python from 5.0.0 to 5.1.0 (#931) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Cody Wang --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index a6106b2d7..dfdb535b1 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index aca79d599..1ba9dd26a 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index a6a359e93..fa07100e9 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9d398f23d..147a1ce24 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 2c30b8ec0..2d48fcc1d 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Python - uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 + uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.x' - name: Install wheel From 24d4d5f03c410cee8f46f73f99c39a0ece99327e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:41:02 -0700 Subject: [PATCH 190/347] infra: bump actions/checkout from 4.1.1 to 4.1.5 (#967) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index dfdb535b1..942e35cc9 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -16,7 +16,7 @@ jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 1ba9dd26a..cfb58c2f8 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -21,7 +21,7 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index fa07100e9..c1de37678 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,7 +12,7 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 147a1ce24..2f6b9992b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 2d48fcc1d..b3bd97833 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -14,7 +14,7 @@ jobs: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: From 96b42001e95ed6ccaeb78087dcec61fe03022044 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:44:59 -0700 Subject: [PATCH 191/347] infra: bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 (#1012) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index c1de37678..129f2f9f7 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # release/v1 + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # release/v1 with: password: ${{ secrets.pypi_token }} From fb307e325aea716e56c888cc6e4ed6394bf90b6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:50:09 -0700 Subject: [PATCH 192/347] infra: bump thehanimo/pr-title-checker from 1.4.1 to 1.4.2 (#1010) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pr-title-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title-checker.yml b/.github/workflows/pr-title-checker.yml index a20344a77..78126bdce 100644 --- a/.github/workflows/pr-title-checker.yml +++ b/.github/workflows/pr-title-checker.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check PR Title" - uses: thehanimo/pr-title-checker@v1.4.1 + uses: thehanimo/pr-title-checker@v1.4.2 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pass_on_octokit_error: false From 9f6adcf11f0778f7fe096236ef7bd9011ef30687 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:53:54 -0400 Subject: [PATCH 193/347] doc: update PR title instructions (#994) --- .github/pull_request_template.md | 2 +- CONTRIBUTING.md | 22 ++++++++++------------ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 87dda8a1f..d4fb6616f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,7 +11,7 @@ _Put an `x` in the boxes that apply. You can also fill these out after creating #### General - [ ] I have read the [CONTRIBUTING](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md) doc -- [ ] I used the commit message format described in [CONTRIBUTING](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#commit-your-change) +- [ ] I used the PR title format described in [CONTRIBUTING](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#PR-title-format) - [ ] I have updated any necessary documentation, including [READMEs](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/README.md) and [API docs](https://github.com/amazon-braket/amazon-braket-sdk-python/blob/main/CONTRIBUTING.md#documentation-guidelines) (if appropriate) #### Tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0df22f51e..5635cd689 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -95,10 +95,18 @@ You can also pass in various pytest arguments `tox -e integ-tests -- your-argume 1. Run `tox`, to run all the unit tests, linters, and documentation creation, and verify that all checks and tests pass. 1. If your changes include documentation changes, please see the [Documentation Guidelines](#documentation-guidelines). +### Send a Pull Request + +GitHub provides additional documentation on [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/). + +Please remember to: +* Use PR titles that follow the guidelines under [PR Title Format](#pr-title-format). +* Send us a pull request, answering any default questions in the pull request interface. +* Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. -### Commit Your Change +#### PR Title Format -We use commit messages to update the project version number and generate changelog entries, so it's important for them to follow the right format. Valid commit messages include a prefix, separated from the rest of the message by a colon and a space. Here are a few examples: +We use commit messages to update the project version number and generate changelog entries. The PR title is used as the commit message when merging a PR, so it's important for PR titles to follow the right format. Valid PR titles include a prefix, separated from the rest of the message by a colon and a space. Here are a few examples: ``` feature: support new parameter for `xyz` @@ -122,16 +130,6 @@ Some of the prefixes allow abbreviation ; `break`, `feat`, `depr`, and `doc` are For the rest of the message, use imperative style and keep things concise but informative. See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) for guidance. -### Send a Pull Request - -GitHub provides additional documentation on [Creating a Pull Request](https://help.github.com/articles/creating-a-pull-request/). - -Please remember to: -* Use commit messages (and PR titles) that follow the guidelines under [Commit Your Change](#commit-your-change). -* Send us a pull request, answering any default questions in the pull request interface. -* Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. - - ```mermaid timeline title Code integration journey (CI) From dabcb5e622487f5b49335249cadc2c221ec03602 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 28 Jun 2024 11:23:48 -0700 Subject: [PATCH 194/347] feat: Use `run_multiple` for local batches (#1005) The Braket SDK can now leverage custom backend implementations for running multiple tasks --- setup.py | 2 +- src/braket/devices/device.py | 2 +- src/braket/devices/local_simulator.py | 175 +++++++----------- .../braket/devices/test_local_simulator.py | 19 +- 4 files changed, 80 insertions(+), 118 deletions(-) diff --git a/setup.py b/setup.py index 54987c283..bf429b266 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.25.0", + "amazon-braket-default-simulator>=1.26.0", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index dbc7b6b35..af3467a40 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -131,7 +131,7 @@ def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: def _apply_noise_model_to_circuit( self, task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation] - ) -> None: + ) -> Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]: if isinstance(task_specification, Circuit): for instruction in task_specification.instructions: if isinstance(instruction.operator, Noise): diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 15ec904de..02b379596 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -16,7 +16,6 @@ import sys from functools import singledispatchmethod from itertools import repeat -from multiprocessing import Pool from os import cpu_count from typing import Any, Optional, Union @@ -31,6 +30,11 @@ from braket.ir.ahs import Program as AHSProgram from braket.ir.openqasm import Program as OpenQASMProgram from braket.simulator import BraketSimulator +from braket.task_result import ( + AnalogHamiltonianSimulationTaskResult, + AnnealingTaskResult, + GateModelTaskResult, +) from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( AnalogHamiltonianSimulationQuantumTaskResult, @@ -118,8 +122,9 @@ def run( """ if self._noise_model: task_specification = self._apply_noise_model_to_circuit(task_specification) - result = self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) - return LocalQuantumTask(result) + payload = self._construct_payload(task_specification, inputs, shots) + result = self._delegate.run(payload, *args, shots=shots, **kwargs) + return LocalQuantumTask(self._to_result_object(result)) def run_batch( # noqa: C901 self, @@ -151,7 +156,7 @@ def run_batch( # noqa: C901 shots (Optional[int]): The number of times to run the quantum task. Default: 0. max_parallel (Optional[int]): The maximum number of quantum tasks to run in parallel. Default - is the number of CPU. + is the number of logical CPUs. inputs (Optional[Union[dict[str, float], list[dict[str, float]]]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. @@ -195,6 +200,7 @@ def run_batch( # noqa: C901 else: tasks_and_inputs = list(tasks_and_inputs) + payloads = [] for task_specification, input_map in tasks_and_inputs: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} @@ -203,12 +209,12 @@ def run_batch( # noqa: C901 f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" ) + payloads.append(self._construct_payload(task_specification, input_map, shots)) - with Pool(min(max_parallel, len(tasks_and_inputs))) as pool: - param_list = [(task, shots, inp, *args, *kwargs) for task, inp in tasks_and_inputs] - results = pool.starmap(self._run_internal_wrap, param_list) - - return LocalQuantumTaskBatch(results) + results = self._delegate.run_multiple( + payloads, *args, shots=shots, max_parallel=max_parallel, **kwargs + ) + return LocalQuantumTaskBatch([self._to_result_object(result) for result in results]) @property def properties(self) -> DeviceCapabilities: @@ -230,21 +236,8 @@ def registered_backends() -> set[str]: """ return set(_simulator_devices.keys()) - def _run_internal_wrap( - self, - task_specification: Union[ - Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram - ], - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: # pragma: no cover - """Wraps _run_interal for pickle dump""" - return self._run_internal(task_specification, shots, inputs=inputs, *args, **kwargs) - @singledispatchmethod - def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulator: + def _get_simulator(self, simulator: Any) -> BraketSimulator: raise TypeError("Simulator must either be a string or a BraketSimulator instance") @_get_simulator.register @@ -261,66 +254,29 @@ def _(self, backend_impl: BraketSimulator): return backend_impl @singledispatchmethod - def _run_internal( + def _construct_payload( self, - task_specification: Union[ - Circuit, - Problem, - OpenQASMProgram, - AnalogHamiltonianSimulation, - AHSProgram, - SerializableProgram, - ], - shots: Optional[int] = None, - *args, - **kwargs, - ) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult]: + task_specification: Any, + inputs: Optional[dict[str, float]], + shots: Optional[int], + ) -> Any: raise NotImplementedError(f"Unsupported task type {type(task_specification)}") - @_run_internal.register - def _( - self, - circuit: Circuit, - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ): + @_construct_payload.register + def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: Optional[int]): simulator = self._delegate if DeviceActionType.OPENQASM in simulator.properties.action: validate_circuit_and_shots(circuit, shots) program = circuit.to_ir(ir_type=IRType.OPENQASM) program.inputs.update(inputs or {}) - results = simulator.run(program, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_object(results) + return program elif DeviceActionType.JAQCD in simulator.properties.action: validate_circuit_and_shots(circuit, shots) - program = circuit.to_ir(ir_type=IRType.JAQCD) - qubits = circuit.qubit_count - results = simulator.run(program, qubits, shots, *args, **kwargs) - return GateModelQuantumTaskResult.from_object(results) + return circuit.to_ir(ir_type=IRType.JAQCD) raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") - @_run_internal.register - def _(self, problem: Problem, shots: Optional[int] = None, *args, **kwargs): - simulator = self._delegate - if DeviceActionType.ANNEALING not in simulator.properties.action: - raise NotImplementedError( - f"{type(simulator)} does not support quantum annealing problems" - ) - ir = problem.to_ir() - results = simulator.run(ir, shots, *args, *kwargs) - return AnnealingQuantumTaskResult.from_object(results) - - @_run_internal.register - def _( - self, - program: OpenQASMProgram, - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ): + @_construct_payload.register + def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots): simulator = self._delegate if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") @@ -331,54 +287,55 @@ def _( source=program.source, inputs=inputs_copy, ) + return program - results = simulator.run(program, shots, *args, **kwargs) - - if isinstance(results, GateModelQuantumTaskResult): - return results + @_construct_payload.register + def _(self, program: SerializableProgram, _inputs, _shots): + return OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM)) - return GateModelQuantumTaskResult.from_object(results) - - @_run_internal.register - def _( - self, - program: SerializableProgram, - shots: Optional[int] = None, - inputs: Optional[dict[str, float]] = None, - *args, - **kwargs, - ): - program = OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM)) - return self._run_internal(program, shots, inputs, *args, **kwargs) - - @_run_internal.register - def _( - self, - program: AnalogHamiltonianSimulation, - shots: Optional[int] = None, - *args, - **kwargs, - ): + @_construct_payload.register + def _(self, program: AnalogHamiltonianSimulation, _inputs, _shots): simulator = self._delegate if DeviceActionType.AHS not in simulator.properties.action: raise NotImplementedError( f"{type(simulator)} does not support analog Hamiltonian simulation programs" ) - results = simulator.run(program.to_ir(), shots, *args, **kwargs) - return AnalogHamiltonianSimulationQuantumTaskResult.from_object(results) + return program.to_ir() - @_run_internal.register - def _( - self, - program: AHSProgram, - shots: Optional[int] = None, - *args, - **kwargs, - ): + @_construct_payload.register + def _(self, program: AHSProgram, _inputs, _shots): simulator = self._delegate if DeviceActionType.AHS not in simulator.properties.action: raise NotImplementedError( f"{type(simulator)} does not support analog Hamiltonian simulation programs" ) - results = simulator.run(program, shots, *args, **kwargs) - return AnalogHamiltonianSimulationQuantumTaskResult.from_object(results) + return program + + @_construct_payload.register + def _(self, problem: Problem, _inputs, _shots): + simulator = self._delegate + if DeviceActionType.ANNEALING not in simulator.properties.action: + raise NotImplementedError( + f"{type(simulator)} does not support quantum annealing problems" + ) + return problem.to_ir() + + @singledispatchmethod + def _to_result_object(self, result: Any) -> Any: + raise NotImplementedError(f"Unsupported task result type {type(result)}") + + @_to_result_object.register + def _(self, result: GateModelQuantumTaskResult): + return result + + @_to_result_object.register + def _(self, result: GateModelTaskResult): + return GateModelQuantumTaskResult.from_object(result) + + @_to_result_object.register + def _(self, result: AnalogHamiltonianSimulationTaskResult): + return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) + + @_to_result_object.register + def _(self, result: AnnealingTaskResult): + return AnnealingQuantumTaskResult.from_object(result) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 216d161c7..451553f02 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -156,7 +156,12 @@ def properties(self) -> DeviceCapabilities: class DummyJaqcdSimulator(BraketSimulator): def run( - self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs + self, + program: ir.jaqcd.Program, + qubits: Optional[int] = None, + shots: Optional[int] = None, + *args, + **kwargs, ) -> dict[str, Any]: if not isinstance(program, ir.jaqcd.Program): raise TypeError("Not a Jaqcd program") @@ -520,7 +525,7 @@ def test_run_gate_model_inputs(): ), inputs={"theta": 2}, ), - 10, + shots=10, ) assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) @@ -543,7 +548,7 @@ def test_run_program_model_inputs(): task = sim.run(program, inputs=update_inputs, shots=10) assert program.inputs == inputs program.inputs.update(update_inputs) - dummy.run.assert_called_with(program, 10) + dummy.run.assert_called_with(program, shots=10) assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) @@ -552,7 +557,7 @@ def test_run_jaqcd_only(): sim = LocalSimulator(dummy) task = sim.run(Circuit().h(0).cnot(0, 1), 10) dummy.assert_shots(10) - dummy.assert_qubits(2) + dummy.assert_qubits(None) assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) @@ -713,7 +718,7 @@ def test_run_with_noise_model(mock_run, noise_model): mock_run.assert_called_with( Program(source=expected_circuit, inputs={}), - 4, + shots=4, ) @@ -753,7 +758,7 @@ def test_run_noisy_circuit_with_noise_model(mock_run, noise_model): mock_run.assert_called_with( Program(source=expected_circuit, inputs={}), - 4, + shots=4, ) assert w[-1].message.__str__() == expected_warning @@ -781,6 +786,6 @@ def test_run_openqasm_with_noise_model(mock_run, noise_model): mock_run.assert_called_with( Program(source=expected_circuit, inputs=None), - 4, + shots=4, ) assert w[-1].message.__str__() == expected_warning From 686aaf8c831c45e7bdecc9d5722dd2ef5151a3c0 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 28 Jun 2024 13:51:31 -0700 Subject: [PATCH 195/347] deprecation: Remove OQC (#1014) --- src/braket/devices/devices.py | 4 ++-- test/integ_tests/test_device_creation.py | 10 +++++----- test/integ_tests/test_measure.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index fa2c6d025..86a32a142 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -37,7 +37,7 @@ class _IonQ(str, Enum): Forte1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Forte-1" class _OQC(str, Enum): - Lucy = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" + _Lucy = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" class _QuEra(str, Enum): Aquila = "arn:aws:braket:us-east-1::device/qpu/quera/Aquila" @@ -58,7 +58,7 @@ class _Xanadu(str, Enum): # DWave = _DWave IonQ = _IonQ IQM = _IQM - OQC = _OQC + # OQC = _OQC QuEra = _QuEra Rigetti = _Rigetti # Xanadu = _Xanadu diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 540c09f61..c7183c3e6 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -19,13 +19,13 @@ RIGETTI_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" +IQM_ARN = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" @pytest.mark.parametrize( - "arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN), (PULSE_ARN)] + "arn", [(RIGETTI_ARN), (IONQ_ARN), (IQM_ARN), (SIMULATOR_ARN), (PULSE_ARN)] ) def test_device_creation(arn, created_braket_devices): device = created_braket_devices[arn] @@ -48,10 +48,10 @@ def test_device_across_regions(aws_session, created_braket_devices): # assert QPUs across different regions can be created using the same aws_session created_braket_devices[RIGETTI_ARN] created_braket_devices[IONQ_ARN] - created_braket_devices[OQC_ARN] + created_braket_devices[IQM_ARN] -@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (OQC_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (IQM_ARN), (SIMULATOR_ARN)]) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn @@ -77,7 +77,7 @@ def test_get_devices_others(): def test_get_devices_all(braket_devices): result_arns = [result.arn for result in braket_devices] - for arn in [RIGETTI_ARN, IONQ_ARN, SIMULATOR_ARN, OQC_ARN]: + for arn in [RIGETTI_ARN, IONQ_ARN, IQM_ARN, SIMULATOR_ARN]: assert arn in result_arns diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py index b7fef275b..9c8fdc02c 100644 --- a/test/integ_tests/test_measure.py +++ b/test/integ_tests/test_measure.py @@ -25,7 +25,7 @@ IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" +IQM_ARN = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" @pytest.mark.parametrize("arn", [(IONQ_ARN), (SIMULATOR_ARN)]) @@ -53,7 +53,7 @@ def test_measure_on_local_sim(sim): assert result.measured_qubits == [0, 1] -@pytest.mark.parametrize("arn", [(OQC_ARN)]) +@pytest.mark.parametrize("arn", [(IQM_ARN)]) def test_measure_on_supported_devices(arn): device = AwsDevice(arn) if not device.is_available: From 1e4a76f0d502ac8fdbbeaf9d786ad3d4402b7b63 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Jun 2024 21:49:49 +0000 Subject: [PATCH 196/347] prepare release v1.83.0 --- CHANGELOG.md | 14 ++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6716703f..e50f15a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v1.83.0 (2024-06-28) + +### Deprecations and Removals + + * Remove OQC + +### Features + + * Use `run_multiple` for local batches + +### Documentation Changes + + * update PR title instructions + ## v1.82.0 (2024-06-27) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4d28af096..ed31be55f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.82.1.dev0" +__version__ = "1.83.0" From e72299b7fa261be5bb62661ec68b04dd368d1804 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 28 Jun 2024 21:49:49 +0000 Subject: [PATCH 197/347] update development version to v1.83.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ed31be55f..f91167665 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.83.0" +__version__ = "1.83.1.dev0" From f42f3221c8cd184f3b3b0935371bd0442efebd79 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:02:15 -0400 Subject: [PATCH 198/347] infra: bump actions/checkout from 4.1.5 to 4.1.7 (#1015) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 942e35cc9..e047484ad 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -16,7 +16,7 @@ jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index cfb58c2f8..ab2656bc0 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -21,7 +21,7 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 129f2f9f7..942974db7 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,7 +12,7 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2f6b9992b..8ce80816f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index b3bd97833..3b439db6d 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -14,7 +14,7 @@ jobs: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: From bbdbfa64cd63ae1b035a48c3eaa6485f43309c60 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 2 Jul 2024 13:28:51 -0700 Subject: [PATCH 199/347] test: Add Garnet to tracker integ test (#1016) --- test/integ_tests/test_cost_tracking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index 0c06f297a..f493c9779 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -28,7 +28,7 @@ "qpu", [ "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", - "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy", + "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet", "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3", ], ) From 66fec48bc641507870768b252e2b9a26046c05af Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 29 Jul 2024 13:12:32 -0400 Subject: [PATCH 200/347] feat: support erf_square and swap_phases (#1019) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/pulse/__init__.py | 1 + src/braket/pulse/ast/approximation_parser.py | 28 +++ src/braket/pulse/pulse_sequence.py | 20 +++ src/braket/pulse/waveforms.py | 162 ++++++++++++++++++ .../pulse/ast/test_approximation_parser.py | 122 ++++++++++++- .../unit_tests/braket/pulse/test_waveforms.py | 106 +++++++++++- 6 files changed, 437 insertions(+), 2 deletions(-) diff --git a/src/braket/pulse/__init__.py b/src/braket/pulse/__init__.py index 01ef66892..48f7022af 100644 --- a/src/braket/pulse/__init__.py +++ b/src/braket/pulse/__init__.py @@ -18,5 +18,6 @@ ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, + ErfSquareWaveform, GaussianWaveform, ) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index d2dcf65e8..16c9c6726 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -26,6 +26,7 @@ from braket.pulse.waveforms import ( ConstantWaveform, DragGaussianWaveform, + ErfSquareWaveform, GaussianWaveform, Waveform, ) @@ -468,6 +469,20 @@ def set_scale(self, node: ast.FunctionCall, context: _ParseState) -> None: value = self.visit(node.arguments[1], context) context.frame_data[frame].scale = value + def swap_phases(self, node: ast.FunctionCall, context: _ParseState) -> None: + """A 'swap_phases' Function call. + + Args: + node (ast.FunctionCall): The function call node. + context (_ParseState): The parse state. + """ + frame1 = self.visit(node.arguments[0], context) + frame2 = self.visit(node.arguments[1], context) + phase1 = context.frame_data[frame1].phase + phase2 = context.frame_data[frame2].phase + context.frame_data[frame1].phase = phase2 + context.frame_data[frame2].phase = phase1 + def capture_v0(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'capture_v0' Function call. @@ -549,6 +564,19 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor args = [self.visit(arg, context) for arg in node.arguments] return DragGaussianWaveform(*args) + def erf_square(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: + """A 'erf_square' Waveform Function call. + + Args: + node (ast.FunctionCall): The function call node. + context (_ParseState): The parse state. + + Returns: + Waveform: The waveform object representing the function call. + """ + args = [self.visit(arg, context) for arg in node.arguments] + return ErfSquareWaveform(*args) + def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: frame_states = { diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 9d43127a0..6c5d60444 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -144,6 +144,26 @@ def shift_phase( self._frames[frame.id] = frame return self + def swap_phases( + self, + frame_1: Frame, + frame_2: Frame, + ) -> PulseSequence: + """Adds an instruction to swap the phases between two frames. + + Args: + frame_1 (Frame): First frame for which to swap the phase. + frame_2 (Frame): Second frame for which to swap the phase. + + Returns: + PulseSequence: self, with the instruction added. + """ + _validate_uniqueness(self._frames, [frame_1, frame_2]) + self._program.function_call("swap_phases", [frame_1, frame_2]) + self._frames[frame_1.id] = frame_1 + self._frames[frame_2.id] = frame_2 + return self + def set_scale( self, frame: Frame, scale: Union[float, FreeParameterExpression] ) -> PulseSequence: diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 915d187a8..582592dc8 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -19,6 +19,7 @@ from typing import Optional, Union import numpy as np +import scipy as sp from oqpy import WaveformVar, bool_, complex128, declare_waveform_generator, duration, float64 from oqpy.base import OQPyExpression @@ -501,6 +502,166 @@ def _from_calibration_schema(waveform_json: dict) -> GaussianWaveform: return GaussianWaveform(**waveform_parameters) +class ErfSquareWaveform(Waveform, Parameterizable): + """A square waveform with smoothed edges.""" + + def __init__( + self, + length: Union[float, FreeParameterExpression], + width: Union[float, FreeParameterExpression], + sigma: Union[float, FreeParameterExpression], + amplitude: Union[float, FreeParameterExpression] = 1, + zero_at_edges: bool = False, + id: Optional[str] = None, + ): + r"""Initializes a `ErfSquareWaveform`. + + .. math:: (\text{step}((t-t_1)/sigma) + \text{step}(-(t-t_2)/sigma) - 1) + + where :math:`\text{step}(t)` is the rounded step function defined as + :math:`(erf(t)+1)/2` and :math:`t_1` and :math:`t_2` are the timestamps at the half + height. The waveform is scaled such that its maximum is equal to `amplitude`. + + Args: + length (Union[float, FreeParameterExpression]): Duration (in seconds) from the start + to the end of the waveform. + width (Union[float, FreeParameterExpression]): Duration (in seconds) between the + half height of the two edges. + sigma (Union[float, FreeParameterExpression]): A characteristic time of how quickly + the edges rise and fall. + amplitude (Union[float, FreeParameterExpression]): The amplitude of the waveform + envelope. Defaults to 1. + zero_at_edges (bool): Whether the waveform is scaled such that it has zero value at the + edges. Defaults to False. + id (Optional[str]): The identifier used for declaring this waveform. A random string of + ascii characters is assigned by default. + """ + self.length = length + self.width = width + self.sigma = sigma + self.amplitude = amplitude + self.zero_at_edges = zero_at_edges + self.id = id or _make_identifier_name() + + def __repr__(self) -> str: + return ( + f"ErfSquareWaveform('id': {self.id}, 'length': {self.length}, " + f"'width': {self.width}, 'sigma': {self.sigma}, 'amplitude': {self.amplitude}, " + f"'zero_at_edges': {self.zero_at_edges})" + ) + + @property + def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + """Returns the parameters associated with the object, either unbound free parameter + expressions or bound values. + """ + return [self.length, self.width, self.sigma, self.amplitude] + + def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ErfSquareWaveform: + """Takes in parameters and returns an object with specified parameters + replaced with their values. + + Args: + **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + + Returns: + ErfSquareWaveform: A copy of this waveform with the requested parameters bound. + """ + constructor_kwargs = { + "length": subs_if_free_parameter(self.length, **kwargs), + "width": subs_if_free_parameter(self.width, **kwargs), + "sigma": subs_if_free_parameter(self.sigma, **kwargs), + "amplitude": subs_if_free_parameter(self.amplitude, **kwargs), + "zero_at_edges": self.zero_at_edges, + "id": self.id, + } + return ErfSquareWaveform(**constructor_kwargs) + + def __eq__(self, other: ErfSquareWaveform): + return isinstance(other, ErfSquareWaveform) and ( + self.length, + self.width, + self.sigma, + self.amplitude, + self.zero_at_edges, + self.id, + ) == ( + other.length, + other.width, + other.sigma, + other.amplitude, + other.zero_at_edges, + other.id, + ) + + def _to_oqpy_expression(self) -> OQPyExpression: + """Returns an OQPyExpression defining this waveform. + + Returns: + OQPyExpression: The OQPyExpression. + """ + erf_square_generator = declare_waveform_generator( + "erf_square", + [ + ("length", duration), + ("width", duration), + ("sigma", duration), + ("amplitude", float64), + ("zero_at_edges", bool_), + ], + ) + return WaveformVar( + init_expression=erf_square_generator( + self.length, + self.width, + self.sigma, + self.amplitude, + self.zero_at_edges, + ), + name=self.id, + ) + + def sample(self, dt: float) -> np.ndarray: + """Generates a sample of amplitudes for this Waveform based on the given time resolution. + + Args: + dt (float): The time resolution. + + Returns: + np.ndarray: The sample amplitudes for this waveform. + """ + sample_range = np.arange(0, self.length, dt) + t1 = (self.length - self.width) / 2 + t2 = (self.length + self.width) / 2 + samples = ( + sp.special.erf((sample_range - t1) / self.sigma) + + sp.special.erf(-(sample_range - t2) / self.sigma) + ) / 2 + + mid_waveform_height = sp.special.erf((self.width / 2) / self.sigma) + waveform_bottom = (sp.special.erf(-t1 / self.sigma) + sp.special.erf(t2 / self.sigma)) / 2 + + if self.zero_at_edges: + return ( + (samples - waveform_bottom) + / (mid_waveform_height - waveform_bottom) + * self.amplitude + ) + else: + return samples * self.amplitude / mid_waveform_height + + @staticmethod + def _from_calibration_schema(waveform_json: dict) -> ErfSquareWaveform: + waveform_parameters = {"id": waveform_json["waveformId"]} + for val in waveform_json["arguments"]: + waveform_parameters[val["name"]] = ( + float(val["value"]) + if val["type"] == "float" + else FreeParameterExpression(val["value"]) + ) + return ErfSquareWaveform(**waveform_parameters) + + def _make_identifier_name() -> str: return "".join([random.choice(string.ascii_letters) for _ in range(10)]) # noqa S311 @@ -511,6 +672,7 @@ def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: "drag_gaussian": DragGaussianWaveform._from_calibration_schema, "gaussian": GaussianWaveform._from_calibration_schema, "constant": ConstantWaveform._from_calibration_schema, + "erf_square": ErfSquareWaveform._from_calibration_schema, } if "amplitudes" in waveform: waveform["name"] = "arbitrary" diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py index 56f02aa12..479a3b53e 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -19,7 +19,13 @@ from openpulse import ast from oqpy import IntVar -from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform +from braket.pulse import ( + ArbitraryWaveform, + ConstantWaveform, + DragGaussianWaveform, + ErfSquareWaveform, + GaussianWaveform, +) from braket.pulse.ast.approximation_parser import _ApproximationParser from braket.pulse.frame import Frame from braket.pulse.port import Port @@ -352,6 +358,46 @@ def test_set_shift_phase_beyond_2_pi(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +def test_swap_phases(port): + phase1 = 0.12 + phase2 = 0.34 + frequency = 1e8 + frame1 = Frame( + frame_id="frame1", port=port, frequency=frequency, phase=phase1, is_predefined=False + ) + frame2 = Frame( + frame_id="frame2", port=port, frequency=frequency, phase=phase2, is_predefined=False + ) + pulse_seq = PulseSequence().delay([], 10e-9).swap_phases(frame1, frame2).delay([], 10e-9) + expected_amplitudes = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries(), "frame2": TimeSeries()} + expected_phases = {"frame1": TimeSeries(), "frame2": TimeSeries()} + + # properties of frame1 before swap + expected_amplitudes["frame1"].put(0, 0).put(9e-9, 0) + expected_frequencies["frame1"].put(0, frequency).put(9e-9, frequency) + expected_phases["frame1"].put(0, phase1).put(9e-9, phase1) + + # properties of frame1 after swap + expected_amplitudes["frame1"].put(10e-9, 0).put(19e-9, 0) + expected_frequencies["frame1"].put(10e-9, frequency).put(19e-9, frequency) + expected_phases["frame1"].put(10e-9, phase2).put(19e-9, phase2) + + # properties of frame2 before swap + expected_amplitudes["frame2"].put(0, 0).put(9e-9, 0) + expected_frequencies["frame2"].put(0, frequency).put(9e-9, frequency) + expected_phases["frame2"].put(0, phase2).put(9e-9, phase2) + + # properties of frame2 after swap + expected_amplitudes["frame2"].put(10e-9, 0).put(19e-9, 0) + expected_frequencies["frame2"].put(10e-9, frequency).put(19e-9, frequency) + expected_phases["frame2"].put(10e-9, phase1).put(19e-9, phase1) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict([frame1, frame2])) + + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + def test_set_shift_frequency(port): frame = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) pulse_seq = ( @@ -647,6 +693,80 @@ def test_play_drag_gaussian_waveforms(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +def test_play_erf_square_waveforms(port): + frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + erf_square_wf_ZaE_False = ErfSquareWaveform( + length=1e-8, width=8e-9, sigma=1e-9, amplitude=0.8, zero_at_edges=False + ) + pulse_seq = PulseSequence().play(frame1, erf_square_wf_ZaE_False) + + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + complex(0.06291968379016318), + complex(0.4000000061669033), + complex(0.7370803285436436), + complex(0.7981289183125405), + complex(0.7999911761342559), + complex(0.8), + complex(0.7999911761342559), + complex(0.7981289183125405), + complex(0.7370803285436436), + complex(0.4000000061669033), + ], + dtype=np.complex128, + ) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(t, v) + expected_frequencies["frame1"].put(t, 1e8) + expected_phases["frame1"].put(t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame1)) + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + +def test_play_erf_square_waveforms_zero_at_edges(port): + frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + erf_square_wf_ZaE_True = ErfSquareWaveform( + length=1e-8, width=8e-9, sigma=1e-9, amplitude=0.8, zero_at_edges=True + ) + pulse_seq = PulseSequence().play(frame1, erf_square_wf_ZaE_True) + + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + complex(4.819981832973268e-17), + complex(0.36585464564844294), + complex(0.731709291296886), + complex(0.7979691964131336), + complex(0.7999904228990518), + complex(0.8), + complex(0.7999904228990518), + complex(0.7979691964131336), + complex(0.731709291296886), + complex(0.36585464564844294), + ], + dtype=np.complex128, + ) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(t, v) + expected_frequencies["frame1"].put(t, 1e8) + expected_phases["frame1"].put(t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame1)) + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + def test_barrier_same_dt(port): frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) frame2 = Frame(frame_id="frame2", port=port, frequency=1e8, phase=0, is_predefined=False) diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 34f989c0b..636cb27af 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -20,7 +20,13 @@ from oqpy import Program from braket.circuits.free_parameter import FreeParameter -from braket.pulse import ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, GaussianWaveform +from braket.pulse import ( + ArbitraryWaveform, + ConstantWaveform, + DragGaussianWaveform, + ErfSquareWaveform, + GaussianWaveform, +) from braket.pulse.ast.qasm_parser import ast_to_qasm from braket.pulse.waveforms import _parse_waveform_from_calibration_schema @@ -294,6 +300,85 @@ def test_gaussian_wf_free_params(): _assert_wf_qasm(wf_3, "waveform gauss_wf = gaussian(600.0ms, 300.0ms, 0.1, false);") +def test_erf_square_waveform(): + length = 4e-9 + width = 0.3 + sigma = 0.2 + amplitude = 0.4 + zero_at_edges = False + id = "erf_square_wf" + wf = ErfSquareWaveform(length, width, sigma, amplitude, zero_at_edges, id) + assert wf.id == id + assert wf.zero_at_edges == zero_at_edges + assert wf.amplitude == amplitude + assert wf.width == width + assert wf.sigma == sigma + assert wf.length == length + + +def test_erf_square_waveform_repr(): + length = 4e-9 + width = 0.3 + sigma = 0.2 + amplitude = 0.4 + zero_at_edges = False + id = "erf_square_wf" + wf = ErfSquareWaveform(length, width, sigma, amplitude, zero_at_edges, id) + repr(wf) + + +def test_erf_square_waveform_default_params(): + length = 4e-9 + width = 0.3 + sigma = 0.2 + wf = ErfSquareWaveform(length, width, sigma) + assert re.match(r"[A-Za-z]{10}", wf.id) + assert wf.zero_at_edges is False + assert wf.amplitude == 1 + assert wf.width == width + assert wf.sigma == sigma + assert wf.length == length + + +def test_erf_square_wf_eq(): + wf = ErfSquareWaveform(4e-9, 0.3, 0.2, 0.7, True, "wf_es") + wf_2 = ErfSquareWaveform(wf.length, wf.width, wf.sigma, wf.amplitude, wf.zero_at_edges, wf.id) + assert wf_2 == wf + for att in ["length", "width", "sigma", "amplitude", "zero_at_edges", "id"]: + wfc = deepcopy(wf_2) + setattr(wfc, att, "wrong_value") + assert wf != wfc + + +def test_erf_square_wf_free_params(): + wf = ErfSquareWaveform( + FreeParameter("length_v"), + FreeParameter("width_x"), + FreeParameter("sigma_y"), + FreeParameter("amp_z"), + id="erf_square_wf", + ) + assert wf.parameters == [ + FreeParameter("length_v"), + FreeParameter("width_x"), + FreeParameter("sigma_y"), + FreeParameter("amp_z"), + ] + + wf_2 = wf.bind_values(length_v=0.6, width_x=0.4) + assert wf_2.parameters == [0.6, 0.4, FreeParameter("sigma_y"), FreeParameter("amp_z")] + _assert_wf_qasm( + wf_2, + "waveform erf_square_wf = erf_square(600.0ms, 400.0ms, sigma_y * 1s, amp_z, false);", + ) + + wf_3 = wf.bind_values(length_v=0.6, width_x=0.3, sigma_y=0.1) + assert wf_3.parameters == [0.6, 0.3, 0.1, FreeParameter("amp_z")] + _assert_wf_qasm( + wf_3, "waveform erf_square_wf = erf_square(600.0ms, 300.0ms, 100.0ms, amp_z, false);" + ) + + def _assert_wf_qasm(waveform, expected_qasm): p = Program(None) p.declare(waveform._to_oqpy_expression()) @@ -357,6 +442,25 @@ def _assert_wf_qasm(waveform, expected_qasm): }, ConstantWaveform(id="wf_constant", length=2.1, iq=0.23), ), + ( + { + "waveformId": "wf_erf_square_0", + "name": "erf_square", + "arguments": [ + {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, + {"name": "width", "value": 3.000000000000000e-8, "type": "float"}, + {"name": "sigma", "value": 5.000000000060144e-9, "type": "float"}, + {"name": "amplitude", "value": 0.4549282253548838, "type": "float"}, + ], + }, + ErfSquareWaveform( + id="wf_erf_square_0", + length=6.000000000000001e-8, + width=3.000000000000000e-8, + sigma=5.000000000060144e-9, + amplitude=0.4549282253548838, + ), + ), ], ) def test_parse_waveform_from_calibration_schema(waveform_json, waveform): From 12ec01bdf59c20b68988d268acb1225b4823c525 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 30 Jul 2024 16:16:59 +0000 Subject: [PATCH 201/347] prepare release v1.84.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e50f15a39..6b9cebdb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.84.0 (2024-07-30) + +### Features + + * support erf_square and swap_phases + ## v1.83.0 (2024-06-28) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f91167665..57262e281 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.83.1.dev0" +__version__ = "1.84.0" From ee6377767995790162c9a793edfd9650b0214f65 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 30 Jul 2024 16:16:59 +0000 Subject: [PATCH 202/347] update development version to v1.84.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 57262e281..97f6351c2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.84.0" +__version__ = "1.84.1.dev0" From b095710bcd0d1deb305287eaf2d10e7b81ee76f0 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 20 Aug 2024 13:36:03 -0700 Subject: [PATCH 203/347] feat: Allow early qubit binding of observables (#1022) --- examples/bell_result_types.py | 10 +- examples/hybrid_job.py | 4 +- examples/hybrid_job_script.py | 4 +- examples/local_noise_simulation.py | 4 +- src/braket/circuits/observable.py | 56 +++-- src/braket/circuits/observables.py | 204 ++++++++++++------ src/braket/circuits/result_types.py | 71 +++--- .../braket/circuits/test_observables.py | 91 ++++++-- 8 files changed, 311 insertions(+), 133 deletions(-) diff --git a/examples/bell_result_types.py b/examples/bell_result_types.py index 2bcb87b79..9271d09fc 100644 --- a/examples/bell_result_types.py +++ b/examples/bell_result_types.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.circuits import Circuit, Observable +from braket.circuits import Circuit, observables from braket.devices import LocalSimulator device = LocalSimulator() @@ -24,7 +24,7 @@ .h(0) .cnot(0, 1) .probability(target=[0]) - .expectation(observable=Observable.Z(), target=[1]) + .expectation(observable=observables.Z(1)) .amplitude(state=["00"]) .state_vector() ) @@ -45,9 +45,9 @@ Circuit() .h(0) .cnot(0, 1) - .expectation(observable=Observable.Y() @ Observable.X(), target=[0, 1]) - .variance(observable=Observable.Y() @ Observable.X(), target=[0, 1]) - .sample(observable=Observable.Y() @ Observable.X(), target=[0, 1]) + .expectation(observable=observables.Y(0) @ observables.X(1)) + .variance(observable=observables.Y(0) @ observables.X(1)) + .sample(observable=observables.Y(0) @ observables.X(1)) ) # When shots>0 for a simulator, probability, expectation, variance are calculated from measurements diff --git a/examples/hybrid_job.py b/examples/hybrid_job.py index 7f54c9955..545c0505c 100644 --- a/examples/hybrid_job.py +++ b/examples/hybrid_job.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from braket.aws import AwsDevice -from braket.circuits import Circuit, FreeParameter, Observable +from braket.circuits import Circuit, FreeParameter, observables from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job from braket.jobs.metrics import log_metric @@ -34,7 +34,7 @@ def run_hybrid_job(num_tasks=1): circ = Circuit() circ.rx(0, FreeParameter("theta")) circ.cnot(0, 1) - circ.expectation(observable=Observable.X(), target=0) + circ.expectation(observable=observables.X(0)) # initial parameter theta = 0.0 diff --git a/examples/hybrid_job_script.py b/examples/hybrid_job_script.py index b544ff9df..f7db6df94 100644 --- a/examples/hybrid_job_script.py +++ b/examples/hybrid_job_script.py @@ -13,7 +13,7 @@ from braket.aws import AwsDevice, AwsQuantumJob -from braket.circuits import Circuit, FreeParameter, Observable +from braket.circuits import Circuit, FreeParameter, observables from braket.devices import Devices from braket.jobs import get_job_device_arn, save_job_result from braket.jobs.metrics import log_metric @@ -27,7 +27,7 @@ def run_hybrid_job(num_tasks: int): circ = Circuit() circ.rx(0, FreeParameter("theta")) circ.cnot(0, 1) - circ.expectation(observable=Observable.X(), target=0) + circ.expectation(observable=observables.X(0)) # initial parameter theta = 0.0 diff --git a/examples/local_noise_simulation.py b/examples/local_noise_simulation.py index 0857bfd0c..8c173dd51 100644 --- a/examples/local_noise_simulation.py +++ b/examples/local_noise_simulation.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.circuits import Circuit, Noise +from braket.circuits import Circuit, noises from braket.devices import LocalSimulator device = LocalSimulator("braket_dm") @@ -23,7 +23,7 @@ circuit = Circuit().x(0).x(1) -noise = Noise.BitFlip(probability=0.1) +noise = noises.BitFlip(probability=0.1) circuit.apply_gate_noise(noise) print("Second example: ") print(circuit) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index d3f3fc862..11ed02eb6 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -16,7 +16,6 @@ import numbers from collections.abc import Sequence from copy import deepcopy -from typing import Union import numpy as np @@ -27,7 +26,7 @@ OpenQASMSerializationProperties, SerializationProperties, ) -from braket.registers.qubit_set import QubitSet +from braket.registers import QubitInput, QubitSet, QubitSetInput class Observable(QuantumOperator): @@ -37,23 +36,36 @@ class Observable(QuantumOperator): `ResultType.Expectation` to specify the measurement basis. """ - def __init__(self, qubit_count: int, ascii_symbols: Sequence[str]): + def __init__( + self, qubit_count: int, ascii_symbols: Sequence[str], targets: QubitSetInput | None = None + ): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + if targets is not None: + targets = QubitSet(targets) + if (num_targets := len(targets)) != qubit_count: + raise ValueError( + f"Length of target {num_targets} does not match qubit count {qubit_count}" + ) + self._targets = targets + else: + self._targets = None self._coef = 1 def _unscaled(self) -> Observable: - return Observable(qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols) + return Observable( + qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols, targets=self.targets + ) def to_ir( self, - target: QubitSet | None = None, + target: QubitSetInput | None = None, ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties | None = None, - ) -> Union[str, list[Union[str, list[list[list[float]]]]]]: + ) -> str | list[str | list[list[list[float]]]]: """Returns the IR representation for the observable Args: - target (QubitSet | None): target qubit(s). Defaults to None. + target (QubitSetInput | None): target qubit(s). Defaults to None. ir_type(IRType) : The IRType to use for converting the result type object to its IR representation. Defaults to IRType.JAQCD. serialization_properties (SerializationProperties | None): The serialization properties @@ -61,7 +73,7 @@ def to_ir( properties supplied must correspond to the supplied `ir_type`. Defaults to None. Returns: - Union[str, list[Union[str, list[list[list[float]]]]]]: The IR representation for + str | list[str | list[list[list[float]]]]: The IR representation for the observable. Raises: @@ -84,27 +96,35 @@ def to_ir( else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") - def _to_jaqcd(self) -> list[Union[str, list[list[list[float]]]]]: + def _to_jaqcd(self) -> list[str | list[list[list[float]]]]: """Returns the JAQCD representation of the observable.""" raise NotImplementedError("to_jaqcd has not been implemented yet.") def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - target: QubitSet | None = None, + targets: QubitSetInput | None = None, ) -> str: """Returns the openqasm string representation of the result type. Args: serialization_properties (OpenQASMSerializationProperties): The serialization properties to use while serializing the object to the IR representation. - target (QubitSet | None): target qubit(s). Defaults to None. + targets (QubitSetInput | None): target qubit(s). Defaults to None. Returns: str: Representing the openqasm representation of the result type. """ raise NotImplementedError("to_openqasm has not been implemented yet.") + @property + def targets(self) -> QubitSet | None: + """QubitSet | None: The target qubits of this observable + + If `None`, this is provided by the enclosing result type. + """ + return self._targets + @property def coefficient(self) -> int: """The coefficient of the observable. @@ -185,7 +205,11 @@ def __sub__(self, other: Observable): return self + (-1 * other) def __repr__(self) -> str: - return f"{self.name}('qubit_count': {self.qubit_count})" + return ( + f"{self.name}('qubit_count': {self._qubit_count})" + if self._targets is None + else f"{self.name}('qubit_count': {self._qubit_count}, 'target': {self._targets})" + ) def __eq__(self, other: Observable) -> bool: if isinstance(other, Observable): @@ -198,8 +222,12 @@ class StandardObservable(Observable): eigenvalues of (+1, -1). """ - def __init__(self, ascii_symbols: Sequence[str]): - super().__init__(qubit_count=1, ascii_symbols=ascii_symbols) + def __init__(self, ascii_symbols: Sequence[str], target: QubitInput | None = None): + super().__init__( + qubit_count=1, + ascii_symbols=ascii_symbols, + targets=[target] if target is not None else None, + ) self._eigenvalues = (1.0, -1.0) # immutable def _unscaled(self) -> StandardObservable: diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index ae4f36a09..9346435a2 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -30,20 +30,28 @@ verify_quantum_operator_matrix_dimensions, ) from braket.circuits.serialization import IRType, OpenQASMSerializationProperties -from braket.registers.qubit_set import QubitSet +from braket.registers import QubitInput, QubitSet, QubitSetInput class H(StandardObservable): """Hadamard operation as an observable.""" - def __init__(self): - """Examples: - >>> Observable.H() + def __init__(self, target: QubitInput | None = None): + """Initializes Hadamard observable. + + Args: + target (QubitInput | None): The target qubit to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. + + Examples: + >>> observables.H(0) + >>> observables.H() """ - super().__init__(ascii_symbols=["H"]) + super().__init__(ascii_symbols=["H"], target=target) def _unscaled(self) -> StandardObservable: - return H() + return H(self._targets) def _to_jaqcd(self) -> list[str]: if self.coefficient != 1: @@ -54,11 +62,11 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - if target: - qubit_target = serialization_properties.format_target(int(target[0])) - return f"{coef_prefix}h({qubit_target})" - else: - return f"{coef_prefix}h all" + targets = target or self._targets + qubit_target = int(targets[0]) if targets else None + if qubit_target is not None: + return f"{coef_prefix}h({serialization_properties.format_target(qubit_target)})" + return f"{coef_prefix}h all" def to_matrix(self) -> np.ndarray: return self.coefficient * ( @@ -76,14 +84,22 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: class I(Observable): # noqa: E742 """Identity operation as an observable.""" - def __init__(self): - """Examples: - >>> Observable.I() + def __init__(self, target: QubitInput | None = None): + """Initializes Identity observable. + + Args: + target (QubitInput | None): The target qubit to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. + + Examples: + >>> observables.I(0) + >>> observables.I() """ - super().__init__(qubit_count=1, ascii_symbols=["I"]) + super().__init__(qubit_count=1, ascii_symbols=["I"], targets=target) def _unscaled(self) -> Observable: - return I() + return I(self._targets) def _to_jaqcd(self) -> list[str]: if self.coefficient != 1: @@ -94,11 +110,11 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - if target: - qubit_target = serialization_properties.format_target(int(target[0])) - return f"{coef_prefix}i({qubit_target})" - else: - return f"{coef_prefix}i all" + targets = target or self._targets + qubit_target = int(targets[0]) if targets else None + if qubit_target is not None: + return f"{coef_prefix}i({serialization_properties.format_target(qubit_target)})" + return f"{coef_prefix}i all" def to_matrix(self) -> np.ndarray: return self.coefficient * np.eye(2, dtype=complex) @@ -126,14 +142,22 @@ def eigenvalue(self, index: int) -> float: class X(StandardObservable): """Pauli-X operation as an observable.""" - def __init__(self): - """Examples: - >>> Observable.X() + def __init__(self, target: QubitInput | None = None): + """Initializes Pauli-X observable. + + Args: + target (QubitInput | None): The target qubit to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. + + Examples: + >>> observables.X(0) + >>> observables.X() """ - super().__init__(ascii_symbols=["X"]) + super().__init__(ascii_symbols=["X"], target=target) def _unscaled(self) -> StandardObservable: - return X() + return X(self._targets) def _to_jaqcd(self) -> list[str]: if self.coefficient != 1: @@ -144,11 +168,11 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - if target: - qubit_target = serialization_properties.format_target(int(target[0])) - return f"{coef_prefix}x({qubit_target})" - else: - return f"{coef_prefix}x all" + targets = target or self._targets + qubit_target = int(targets[0]) if targets else None + if qubit_target is not None: + return f"{coef_prefix}x({serialization_properties.format_target(qubit_target)})" + return f"{coef_prefix}x all" def to_matrix(self) -> np.ndarray: return self.coefficient * np.array([[0.0, 1.0], [1.0, 0.0]], dtype=complex) @@ -164,14 +188,22 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: class Y(StandardObservable): """Pauli-Y operation as an observable.""" - def __init__(self): - """Examples: - >>> Observable.Y() + def __init__(self, target: QubitInput | None = None): + """Initializes Pauli-Y observable. + + Args: + target (QubitInput | None): The target qubit to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. + + Examples: + >>> observables.Y(0) + >>> observables.Y() """ - super().__init__(ascii_symbols=["Y"]) + super().__init__(ascii_symbols=["Y"], target=target) def _unscaled(self) -> StandardObservable: - return Y() + return Y(self._targets) def _to_jaqcd(self) -> list[str]: if self.coefficient != 1: @@ -182,11 +214,11 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - if target: - qubit_target = serialization_properties.format_target(int(target[0])) - return f"{coef_prefix}y({qubit_target})" - else: - return f"{coef_prefix}y all" + targets = target or self._targets + qubit_target = int(targets[0]) if targets else None + if qubit_target is not None: + return f"{coef_prefix}y({serialization_properties.format_target(qubit_target)})" + return f"{coef_prefix}y all" def to_matrix(self) -> np.ndarray: return self.coefficient * np.array([[0.0, -1.0j], [1.0j, 0.0]], dtype=complex) @@ -202,14 +234,22 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: class Z(StandardObservable): """Pauli-Z operation as an observable.""" - def __init__(self): - """Examples: - >>> Observable.Z() + def __init__(self, target: QubitInput | None = None): + """Initializes Pauli-Z observable. + + Args: + target (QubitInput | None): The target qubit to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. + + Examples: + >>> observables.Z(0) + >>> observables.Z() """ - super().__init__(ascii_symbols=["Z"]) + super().__init__(ascii_symbols=["Z"], target=target) def _unscaled(self) -> StandardObservable: - return Z() + return Z(self._targets) def _to_jaqcd(self) -> list[str]: if self.coefficient != 1: @@ -220,11 +260,11 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" - if target: - qubit_target = serialization_properties.format_target(int(target[0])) - return f"{coef_prefix}z({qubit_target})" - else: - return f"{coef_prefix}z all" + targets = target or self._targets + qubit_target = int(targets[0]) if targets else None + if qubit_target is not None: + return f"{coef_prefix}z({serialization_properties.format_target(qubit_target)})" + return f"{coef_prefix}z all" def to_matrix(self) -> np.ndarray: return self.coefficient * np.array([[1.0, 0.0], [0.0, -1.0]], dtype=complex) @@ -247,13 +287,13 @@ def __init__(self, observables: list[Observable]): observables (list[Observable]): List of observables for tensor product Examples: - >>> t1 = Observable.Y() @ Observable.X() + >>> t1 = Observable.Y(0) @ Observable.X(1) >>> t1.to_matrix() array([[0.+0.j, 0.+0.j, 0.-0.j, 0.-1.j], [0.+0.j, 0.+0.j, 0.-1.j, 0.-0.j], [0.+0.j, 0.+1.j, 0.+0.j, 0.+0.j], [0.+1.j, 0.+0.j, 0.+0.j, 0.+0.j]]) - >>> t2 = Observable.Z() @ t1 + >>> t2 = Observable.Z(3) @ t1 >>> t2.factors (Z('qubit_count': 1), Y('qubit_count': 1), X('qubit_count': 1)) @@ -281,7 +321,22 @@ def __init__(self, observables: list[Observable]): f"{coefficient if coefficient != 1 else ''}" f"{'@'.join([obs.ascii_symbols[0] for obs in unscaled_factors])}" ) - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + all_targets = [factor.targets for factor in unscaled_factors] + if all(targets is None for targets in all_targets): + merged_targets = None + elif all(targets is not None for targets in all_targets): + flat_targets = [qubit for target in all_targets for qubit in target] + merged_targets = QubitSet(flat_targets) + if len(merged_targets) != len(flat_targets): + raise ValueError("Cannot have repeated target qubits") + else: + raise ValueError("Cannot mix factors with and without targets") + + super().__init__( + qubit_count=qubit_count, + ascii_symbols=[display_name] * qubit_count, + targets=merged_targets, + ) self._coef = coefficient self._factors = unscaled_factors self._factor_dimensions = tuple( @@ -316,7 +371,7 @@ def _to_openqasm( ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" factors = [] - use_qubits = iter(target) + use_qubits = iter(target or self._targets) for obs in self._factors: obs_target = QubitSet() num_qubits = int(np.log2(obs.to_matrix().shape[0])) @@ -438,7 +493,7 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni observable for circuit diagrams. Defaults to `Hamiltonian`. Examples: - >>> t1 = -3 * Observable.Y() + 2 * Observable.X() + >>> t1 = -3 * Observable.Y(0) + 2 * Observable.X(0) Sum(X('qubit_count': 1), Y('qubit_count': 1)) >>> t1.summands (X('qubit_count': 1), Y('qubit_count': 1)) @@ -452,7 +507,15 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni self._summands = tuple(flattened_observables) qubit_count = max(flattened_observables, key=lambda obs: obs.qubit_count).qubit_count + all_targets = [observable.targets for observable in flattened_observables] + if all(targets is None for targets in all_targets): + targets = None + elif all(targets is not None for targets in all_targets): + targets = all_targets + else: + raise ValueError("Cannot mix terms with and without targets") super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + self._targets = targets def __mul__(self, other: numbers.Number) -> Observable: """Scalar multiplication""" @@ -469,8 +532,9 @@ def _to_jaqcd(self) -> list[str]: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - target: list[QubitSet] = None, + target: list[QubitSetInput] = None, ) -> str: + target = target or self._targets if len(self.summands) != len(target): raise ValueError( f"Invalid target of length {len(target)} for Sum with {len(self.summands)} terms" @@ -529,21 +593,30 @@ class Hermitian(Observable): # Cache of eigenpairs _eigenpairs: ClassVar = {} - def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): + def __init__( + self, + matrix: np.ndarray, + display_name: str = "Hermitian", + targets: QubitSetInput | None = None, + ): """Inits a `Hermitian`. Args: matrix (np.ndarray): Hermitian matrix that defines the observable. display_name (str): Name to use for an instance of this Hermitian matrix observable for circuit diagrams. Defaults to `Hermitian`. + targets (QubitSetInput | None): The target qubits to measure the observable on. + If not provided, this needs to be provided from the enclosing result type. + Default: `None`. Raises: ValueError: If `matrix` is not a two-dimensional square matrix, - or has a dimension length that is not a positive power of 2, - or is not Hermitian. + is not Hermitian, or has a dimension length that is either not a positive power of 2 + or, if targets is supplied, doesn't match the size of targets. Examples: - >>> Observable.Hermitian(matrix=np.array([[0, 1],[1, 0]])) + >>> observables.Hermitian(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) + >>> observables.Hermitian(matrix=np.array([[0, 1],[1, 0]])) """ verify_quantum_operator_matrix_dimensions(matrix) self._matrix = np.array(matrix, dtype=complex) @@ -557,10 +630,14 @@ def __init__(self, matrix: np.ndarray, display_name: str = "Hermitian"): Gate.Unitary(matrix=eigendecomposition["eigenvectors"].conj().T), ) - super().__init__(qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count) + super().__init__( + qubit_count=qubit_count, ascii_symbols=[display_name] * qubit_count, targets=targets + ) def _unscaled(self) -> Observable: - return Hermitian(matrix=self._matrix, display_name=self.ascii_symbols[0]) + return Hermitian( + matrix=self._matrix, display_name=self.ascii_symbols[0], targets=self._targets + ) def _to_jaqcd(self) -> list[list[list[list[float]]]]: if self.coefficient != 1: @@ -573,6 +650,7 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" + target = target or self._targets if target: qubit_target = ", ".join( [serialization_properties.format_target(int(t)) for t in target] diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 325fa8f46..f682b9ba1 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -96,7 +96,7 @@ def __init__(self, target: QubitSetInput | None = None): full density matrix is returned. Examples: - >>> ResultType.DensityMatrix(target=[0, 1]) + >>> result_types.DensityMatrix(target=[0, 1]) """ self._target = QubitSet(target) ascii_symbols = ["DensityMatrix"] * len(self._target) if self._target else ["DensityMatrix"] @@ -198,14 +198,15 @@ def __init__( Examples: - >>> ResultType.AdjointGradient(observable=Observable.Z(), + >>> result_types.AdjointGradient(observable=observables.Z(0), + parameters=["alpha", "beta"]) + >>> result_types.AdjointGradient(observable=observables.Z(), target=0, parameters=["alpha", "beta"]) - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> hamiltonian = Observable.Y() @ Observable.Z() + Observable.H() - >>> ResultType.AdjointGradient( + >>> tensor_product = observables.Y(0) @ observables.Z(1) + >>> hamiltonian = observables.Y(0) @ observables.Z(1) + observables.H(0) + >>> result_types.AdjointGradient( >>> observable=tensor_product, - >>> target=[[0, 1], [2]], >>> parameters=["alpha", "beta"], >>> ) """ @@ -261,7 +262,7 @@ def adjoint_gradient( Examples: >>> alpha, beta = FreeParameter('alpha'), FreeParameter('beta') >>> circ = Circuit().h(0).h(1).rx(0, alpha).yy(0, 1, beta).adjoint_gradient( - >>> observable=Observable.Z(), target=[0], parameters=[alpha, beta] + >>> observable=observables.Z(0), parameters=[alpha, beta] >>> ) """ return ResultType.AdjointGradient( @@ -288,7 +289,7 @@ def __init__(self, state: list[str]): state is not a list of strings of '0' and '1' Examples: - >>> ResultType.Amplitude(state=['01', '10']) + >>> result_types.Amplitude(state=['01', '10']) """ if ( not state @@ -367,7 +368,7 @@ def __init__(self, target: QubitSetInput | None = None): circuit. Examples: - >>> ResultType.Probability(target=[0, 1]) + >>> result_types.Probability(target=[0, 1]) """ self._target = QubitSet(target) ascii_symbols = ["Probability"] * len(self._target) if self._target else ["Probability"] @@ -453,16 +454,18 @@ def __init__(self, observable: Observable, target: QubitSetInput | None = None): Args: observable (Observable): the observable for the result type - target (QubitSetInput | None): Target qubits that the - result type is requested for. Default is `None`, which means the observable must - operate only on 1 qubit and it is applied to all qubits in parallel. - + target (QubitSetInput | None): Target qubits that the result type is requested for. + If not provided, the observable's target will be used instead. If neither exist, + then it is applied to all qubits in parallel; in this case the observable must + operate only on 1 qubit. + Default: `None`. Examples: - >>> ResultType.Expectation(observable=Observable.Z(), target=0) + >>> result_types.Expectation(observable=observables.Z(0)) + >>> result_types.Expectation(observable=observables.Z(), target=0) - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Expectation(observable=tensor_product, target=[0, 1]) + >>> tensor_product = observables.Y(0) @ observables.Z(1) + >>> result_types.Expectation(observable=tensor_product) """ super().__init__( ascii_symbols=[f"Expectation({obs_ascii})" for obs_ascii in observable.ascii_symbols], @@ -501,7 +504,7 @@ def expectation(observable: Observable, target: QubitSetInput | None = None) -> ResultType: expectation as a requested result type Examples: - >>> circ = Circuit().expectation(observable=Observable.Z(), target=0) + >>> circ = Circuit().expectation(observable=observables.Z(0)) """ return ResultType.Expectation(observable=observable, target=target) @@ -526,15 +529,18 @@ def __init__(self, observable: Observable, target: QubitSetInput | None = None): Args: observable (Observable): the observable for the result type - target (QubitSetInput | None): Target qubits that the - result type is requested for. Default is `None`, which means the observable must - operate only on 1 qubit and it is applied to all qubits in parallel. + target (QubitSetInput | None): Target qubits that the result type is requested for. + If not provided, the observable's target will be used instead. If neither exist, + then it is applied to all qubits in parallel; in this case the observable must + operate only on 1 qubit. + Default: `None`. Examples: - >>> ResultType.Sample(observable=Observable.Z(), target=0) + >>> result_types.Sample(observable=observables.Z(0)) + >>> result_types.Sample(observable=observables.Z(), target=0) - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Sample(observable=tensor_product, target=[0, 1]) + >>> tensor_product = observables.Y(0) @ observables.Z(1) + >>> result_types.Sample(observable=tensor_product) """ super().__init__( ascii_symbols=[f"Sample({obs_ascii})" for obs_ascii in observable.ascii_symbols], @@ -573,7 +579,7 @@ def sample(observable: Observable, target: QubitSetInput | None = None) -> Resul ResultType: sample as a requested result type Examples: - >>> circ = Circuit().sample(observable=Observable.Z(), target=0) + >>> circ = Circuit().sample(observable=observables.Z(0)) """ return ResultType.Sample(observable=observable, target=target) @@ -599,19 +605,22 @@ def __init__(self, observable: Observable, target: QubitSetInput | None = None): Args: observable (Observable): the observable for the result type - target (QubitSetInput | None): Target qubits that the - result type is requested for. Default is `None`, which means the observable must - operate only on 1 qubit and it is applied to all qubits in parallel. + target (QubitSetInput | None): Target qubits that the result type is requested for. + If not provided, the observable's target will be used instead. If neither exist, + then it is applied to all qubits in parallel; in this case the observable must + operate only on 1 qubit. + Default: `None`. Raises: ValueError: If the observable's qubit count does not equal the number of target qubits, or if `target=None` and the observable's qubit count is not 1. Examples: - >>> ResultType.Variance(observable=Observable.Z(), target=0) + >>> result_types.Variance(observable=observables.Z(0)) + >>> result_types.Variance(observable=observables.Z(), target=0) - >>> tensor_product = Observable.Y() @ Observable.Z() - >>> ResultType.Variance(observable=tensor_product, target=[0, 1]) + >>> tensor_product = observables.Y(0) @ observables.Z(1) + >>> result_types.Variance(observable=tensor_product) """ super().__init__( ascii_symbols=[f"Variance({obs_ascii})" for obs_ascii in observable.ascii_symbols], @@ -650,7 +659,7 @@ def variance(observable: Observable, target: QubitSetInput | None = None) -> Res ResultType: variance as a requested result type Examples: - >>> circ = Circuit().variance(observable=Observable.Z(), target=0) + >>> circ = Circuit().variance(observable=observables.Z(0)) """ return ResultType.Variance(observable=observable, target=target) diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index b6430d4d8..213a29705 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -61,100 +61,116 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv @pytest.mark.parametrize( - "observable, serialization_properties, target, expected_ir", + "observable, observable_with_targets, serialization_properties, target, expected_ir", [ ( Observable.I(), + Observable.I(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "i(q[3])", ), ( Observable.I(), + Observable.I(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "i($3)", ), ( Observable.I(), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "i all", ), ( Observable.X(), + Observable.X(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "x(q[3])", ), ( Observable.X(), + Observable.X(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "x($3)", ), ( Observable.X(), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "x all", ), ( Observable.Y(), + Observable.Y(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "y(q[3])", ), ( Observable.Y(), + Observable.Y(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "y($3)", ), ( Observable.Y(), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "y all", ), ( Observable.Z(), + Observable.Z(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "z(q[3])", ), ( Observable.Z(), + Observable.Z(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "z($3)", ), ( Observable.Z(), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "z all", ), ( Observable.H(), + Observable.H(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "h(q[3])", ), ( Observable.H(), + Observable.H(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "h($3)", ), ( Observable.H(), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "h all", ), ( Observable.Hermitian(np.eye(4)), + Observable.Hermitian(np.eye(4), targets=[1, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [1, 2], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " @@ -162,6 +178,7 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv ), ( Observable.Hermitian(np.eye(4)), + Observable.Hermitian(np.eye(4), targets=[1, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [1, 2], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " @@ -169,36 +186,42 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv ), ( Observable.Hermitian(np.eye(2)), + None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "hermitian([[1+0im, 0im], [0im, 1+0im]]) all", ), ( Observable.H() @ Observable.Z(), + Observable.H(3) @ Observable.Z(0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0], "h(q[3]) @ z(q[0])", ), ( Observable.H() @ Observable.Z(), + Observable.H(3) @ Observable.Z(0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0], "h($3) @ z($0)", ), ( Observable.H() @ Observable.Z() @ Observable.I(), + Observable.H(3) @ Observable.Z(0) @ Observable.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "h(q[3]) @ z(q[0]) @ i(q[1])", ), ( Observable.H() @ Observable.Z() @ Observable.I(), + Observable.H(3) @ Observable.Z(0) @ Observable.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "h($3) @ z($0) @ i($1)", ), ( Observable.Hermitian(np.eye(4)) @ Observable.I(), + Observable.Hermitian(np.eye(4), targets=[3, 0]) @ Observable.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " @@ -207,32 +230,23 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv ), ( Observable.I() @ Observable.Hermitian(np.eye(4)), + Observable.I(3) @ Observable.Hermitian(np.eye(4), targets=[0, 1]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "i($3) @ " "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) $0, $1", ), - ( - (2 * Observable.Z()) @ (3 * Observable.H()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 3], - "6 * z($3) @ h($3)", - ), - ( - (2 * Observable.Z()) @ (3 * Observable.H()) @ (2 * Observable.Y()), - OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - [3, 3, 1], - "12 * z($3) @ h($3) @ y($1)", - ), ( 3 * (2 * Observable.Z()), + 3 * (2 * Observable.Z(3)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "6 * z($3)", ), ( (2 * Observable.I()) @ (2 * Observable.Hermitian(np.eye(4))), + (2 * Observable.I(3)) @ (2 * Observable.Hermitian(np.eye(4), targets=[0, 1])), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "4 * i($3) @ " @@ -241,55 +255,84 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv ), ( Observable.Z() + 2 * Observable.H(), + Observable.Z(3) + 2 * Observable.H(4), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [4]], "z($3) + 2 * h($4)", ), ( 3 * (Observable.H() + 2 * Observable.X()), + 3 * (Observable.H(3) + 2 * Observable.X(0)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [0]], "3 * h($3) + 6 * x($0)", ), ( 3 * (Observable.H() + 2 * Observable.H()), + 3 * (Observable.H(3) + 2 * Observable.H(3)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [3]], "3 * h($3) + 6 * h($3)", ), ( 3 * (Observable.H() + 2 * Observable.H()), + 3 * (Observable.H(3) + 2 * Observable.H(5)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [5]], "3 * h($3) + 6 * h($5)", ), ( (2 * Observable.Y()) @ (3 * Observable.I()) + 0.75 * Observable.Y() @ Observable.Z(), + (2 * Observable.Y(0)) @ (3 * Observable.I(1)) + + 0.75 * Observable.Y(0) @ Observable.Z(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0, 1], [0, 1]], "6 * y($0) @ i($1) + 0.75 * y($0) @ z($1)", ), ( (-2 * Observable.Y()) @ (3 * Observable.I()) + -0.75 * Observable.Y() @ Observable.Z(), + (-2 * Observable.Y(0)) @ (3 * Observable.I(1)) + + -0.75 * Observable.Y(0) @ Observable.Z(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0, 1], [0, 1]], "-6 * y($0) @ i($1) - 0.75 * y($0) @ z($1)", ), ( 4 * (2 * Observable.Z() + 2 * (3 * Observable.X() @ (2 * Observable.Y()))), + 4 * (2 * Observable.Z(0) + 2 * (3 * Observable.X(1) @ (2 * Observable.Y(2)))), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0], [1, 2]], "8 * z($0) + 48 * x($1) @ y($2)", ), + ( + 4 * (2 * Observable.Z(0) + 2 * (3 * Observable.X(1) @ (2 * Observable.Y(2)))), + None, + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + [[5], [4, 3]], + "8 * z($5) + 48 * x($4) @ y($3)", + ), ], ) -def test_observables_to_ir_openqasm(observable, serialization_properties, target, expected_ir): +def test_observables_to_ir_openqasm( + observable, + observable_with_targets, + serialization_properties, + target, + expected_ir, +): assert ( observable.to_ir( target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties ) == expected_ir ) + if observable_with_targets: + assert ( + observable_with_targets.to_ir( + None, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) @pytest.mark.parametrize( @@ -458,6 +501,11 @@ def test_hermitian_eigenvalues(matrix, eigenvalues): compare_eigenvalues(Observable.Hermitian(matrix=matrix), eigenvalues) +def test_hermitian_matrix_target_mismatch(): + with pytest.raises(ValueError): + Observable.Hermitian(np.eye(4), targets=[0, 1, 2]) + + def test_flattened_tensor_product(): observable_one = Observable.Z() @ Observable.Y() observable_two = Observable.X() @ Observable.H() @@ -612,6 +660,16 @@ def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): assert observable.basis_rotation_gates == basis_rotation_gates +def test_tensor_product_repeated_qubits(): + with pytest.raises(ValueError): + (2 * Observable.Z(3)) @ (3 * Observable.H(3)) + + +def test_tensor_product_with_and_without_targets(): + with pytest.raises(ValueError): + (2 * Observable.Z(3)) @ (3 * Observable.H()) + + def test_observable_from_ir_tensor_product(): expected_observable = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) actual_observable = observable_from_ir(["z", "i", "x"]) @@ -714,3 +772,8 @@ def test_unscaled_tensor_product(): observable = 3 * ((2 * Observable.X()) @ (5 * Observable.Y())) assert observable == 30 * (Observable.X() @ Observable.Y()) assert observable._unscaled() == Observable.X() @ Observable.Y() + + +def test_sum_with_and_without_targets(): + with pytest.raises(ValueError): + Observable.X() + 3 * Observable.Y(4) From 6c6caf355262450ade4a890626592e246748e206 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Aug 2024 21:48:18 +0000 Subject: [PATCH 204/347] prepare release v1.85.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b9cebdb4..cd55752ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.85.0 (2024-08-20) + +### Features + + * Allow early qubit binding of observables + ## v1.84.0 (2024-07-30) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 97f6351c2..8dace9a1d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.84.1.dev0" +__version__ = "1.85.0" From f87be27d4676e07ecdc51cfc663c4f0e3c38af4b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 20 Aug 2024 21:48:18 +0000 Subject: [PATCH 205/347] update development version to v1.85.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8dace9a1d..7ae9064d1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.85.0" +__version__ = "1.85.1.dev0" From a79dccc15629b130e7babb5dc1e75a5fa4a1752e Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 22 Aug 2024 21:07:06 -0400 Subject: [PATCH 206/347] feat: add off_center to erf_square (#1023) --- src/braket/pulse/waveforms.py | 20 +++++-- .../pulse/ast/test_approximation_parser.py | 41 ++++++++++++++- .../braket/pulse/test_pulse_sequence.py | 52 ++++++++++++++++++- .../unit_tests/braket/pulse/test_waveforms.py | 41 +++++++++++---- 4 files changed, 134 insertions(+), 20 deletions(-) diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 582592dc8..9da7bee4c 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -510,6 +510,7 @@ def __init__( length: Union[float, FreeParameterExpression], width: Union[float, FreeParameterExpression], sigma: Union[float, FreeParameterExpression], + off_center: Union[float, FreeParameterExpression] = 0, amplitude: Union[float, FreeParameterExpression] = 1, zero_at_edges: bool = False, id: Optional[str] = None, @@ -529,6 +530,9 @@ def __init__( half height of the two edges. sigma (Union[float, FreeParameterExpression]): A characteristic time of how quickly the edges rise and fall. + off_center (Union[float, FreeParameterExpression]): Shift the smoothed square waveform + earlier or later in time. When positive, the smoothed square is shifted later + (to the right), otherwise earlier (to the left). Defaults to 0. amplitude (Union[float, FreeParameterExpression]): The amplitude of the waveform envelope. Defaults to 1. zero_at_edges (bool): Whether the waveform is scaled such that it has zero value at the @@ -539,6 +543,7 @@ def __init__( self.length = length self.width = width self.sigma = sigma + self.off_center = off_center self.amplitude = amplitude self.zero_at_edges = zero_at_edges self.id = id or _make_identifier_name() @@ -546,8 +551,8 @@ def __init__( def __repr__(self) -> str: return ( f"ErfSquareWaveform('id': {self.id}, 'length': {self.length}, " - f"'width': {self.width}, 'sigma': {self.sigma}, 'amplitude': {self.amplitude}, " - f"'zero_at_edges': {self.zero_at_edges})" + f"'width': {self.width}, 'sigma': {self.sigma}, 'off_center': {self.off_center}, " + f"'amplitude': {self.amplitude}, 'zero_at_edges': {self.zero_at_edges})" ) @property @@ -555,7 +560,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. """ - return [self.length, self.width, self.sigma, self.amplitude] + return [self.length, self.width, self.sigma, self.off_center, self.amplitude] def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ErfSquareWaveform: """Takes in parameters and returns an object with specified parameters @@ -571,6 +576,7 @@ def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ErfSquareWaveform: "length": subs_if_free_parameter(self.length, **kwargs), "width": subs_if_free_parameter(self.width, **kwargs), "sigma": subs_if_free_parameter(self.sigma, **kwargs), + "off_center": subs_if_free_parameter(self.off_center, **kwargs), "amplitude": subs_if_free_parameter(self.amplitude, **kwargs), "zero_at_edges": self.zero_at_edges, "id": self.id, @@ -582,6 +588,7 @@ def __eq__(self, other: ErfSquareWaveform): self.length, self.width, self.sigma, + self.off_center, self.amplitude, self.zero_at_edges, self.id, @@ -589,6 +596,7 @@ def __eq__(self, other: ErfSquareWaveform): other.length, other.width, other.sigma, + other.off_center, other.amplitude, other.zero_at_edges, other.id, @@ -606,6 +614,7 @@ def _to_oqpy_expression(self) -> OQPyExpression: ("length", duration), ("width", duration), ("sigma", duration), + ("off_center", duration), ("amplitude", float64), ("zero_at_edges", bool_), ], @@ -615,6 +624,7 @@ def _to_oqpy_expression(self) -> OQPyExpression: self.length, self.width, self.sigma, + self.off_center, self.amplitude, self.zero_at_edges, ), @@ -631,8 +641,8 @@ def sample(self, dt: float) -> np.ndarray: np.ndarray: The sample amplitudes for this waveform. """ sample_range = np.arange(0, self.length, dt) - t1 = (self.length - self.width) / 2 - t2 = (self.length + self.width) / 2 + t1 = (self.length - self.width) / 2 + self.off_center + t2 = (self.length + self.width) / 2 + self.off_center samples = ( sp.special.erf((sample_range - t1) / self.sigma) + sp.special.erf(-(sample_range - t2) / self.sigma) diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py index 479a3b53e..4c0e2105c 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -696,7 +696,7 @@ def test_play_drag_gaussian_waveforms(port): def test_play_erf_square_waveforms(port): frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) erf_square_wf_ZaE_False = ErfSquareWaveform( - length=1e-8, width=8e-9, sigma=1e-9, amplitude=0.8, zero_at_edges=False + length=1e-8, width=8e-9, sigma=1e-9, off_center=0.0, amplitude=0.8, zero_at_edges=False ) pulse_seq = PulseSequence().play(frame1, erf_square_wf_ZaE_False) @@ -733,7 +733,7 @@ def test_play_erf_square_waveforms(port): def test_play_erf_square_waveforms_zero_at_edges(port): frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) erf_square_wf_ZaE_True = ErfSquareWaveform( - length=1e-8, width=8e-9, sigma=1e-9, amplitude=0.8, zero_at_edges=True + length=1e-8, width=8e-9, sigma=1e-9, off_center=0.0, amplitude=0.8, zero_at_edges=True ) pulse_seq = PulseSequence().play(frame1, erf_square_wf_ZaE_True) @@ -767,6 +767,43 @@ def test_play_erf_square_waveforms_zero_at_edges(port): verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) +def test_play_erf_square_waveforms_off_center(port): + frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) + erf_square_wf_ZaE_False = ErfSquareWaveform( + length=1e-8, width=8e-9, sigma=1e-9, off_center=-2e-9, amplitude=0.8, zero_at_edges=False + ) + pulse_seq = PulseSequence().play(frame1, erf_square_wf_ZaE_False) + + times = np.arange(0, 1e-8, port.dt) + values = np.array( + [ + complex(0.7370803285436436), + complex(0.7981289183125405), + complex(0.7999911761342559), + complex(0.8), + complex(0.7999911761342559), + complex(0.7981289183125405), + complex(0.7370803285436436), + complex(0.4000000061669036), + complex(0.0629196837901632), + complex(0.0018710940212660543), + ], + dtype=np.complex128, + ) + + expected_amplitudes = {"frame1": TimeSeries()} + expected_frequencies = {"frame1": TimeSeries()} + expected_phases = {"frame1": TimeSeries()} + + for t, v in zip(times, values): + expected_amplitudes["frame1"].put(t, v) + expected_frequencies["frame1"].put(t, 1e8) + expected_phases["frame1"].put(t, 0) + + parser = _ApproximationParser(program=pulse_seq._program, frames=to_dict(frame1)) + verify_results(parser, expected_amplitudes, expected_frequencies, expected_phases) + + def test_barrier_same_dt(port): frame1 = Frame(frame_id="frame1", port=port, frequency=1e8, phase=0, is_predefined=False) frame2 = Frame(frame_id="frame2", port=port, frequency=1e8, phase=0, is_predefined=False) diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index 9b6092db1..fa6eca395 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -18,6 +18,7 @@ ArbitraryWaveform, ConstantWaveform, DragGaussianWaveform, + ErfSquareWaveform, Frame, GaussianWaveform, Port, @@ -119,6 +120,16 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined predefined_frame_2, ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), ) + .play( + predefined_frame_1, + ErfSquareWaveform( + length=FreeParameter("length_es"), + width=FreeParameter("width_es"), + sigma=2e-9, + off_center=8e-9, + id="erf_square_wf", + ), + ) .capture_v0(predefined_frame_2) ) expected_str_unbound = "\n".join( @@ -135,6 +146,8 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " sigma_dg * 1s, 0.2, 1, false);", " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(length_es * 1s, width_es * 1s, 2.0ns," + " 8.0ns, 1, false);", " set_frequency(predefined_frame_1, a + 2.0 * c);", " shift_frequency(predefined_frame_1, a + 2.0 * c);", " set_phase(predefined_frame_1, a + 2.0 * c);", @@ -149,6 +162,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " play(predefined_frame_2, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", " psb[1] = capture_v0(predefined_frame_2);", "}", ] @@ -162,11 +176,29 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined FreeParameter("sigma_g"), FreeParameter("sigma_dg"), FreeParameter("length_c"), + FreeParameter("width_es"), + FreeParameter("length_es"), } b_bound = pulse_sequence.make_bound_pulse_sequence( - {"c": 2, "length_g": 1e-3, "length_dg": 3e-3, "sigma_dg": 0.4, "length_c": 4e-3} + { + "c": 2, + "length_g": 1e-3, + "length_dg": 3e-3, + "sigma_dg": 0.4, + "length_c": 4e-3, + "length_es": 20e-9, + "width_es": 12e-9, + } + ) + b_bound_call = pulse_sequence( + c=2, + length_g=1e-3, + length_dg=3e-3, + sigma_dg=0.4, + length_c=4e-3, + length_es=20e-9, + width_es=12e-9, ) - b_bound_call = pulse_sequence(c=2, length_g=1e-3, length_dg=3e-3, sigma_dg=0.4, length_c=4e-3) expected_str_b_bound = "\n".join( [ "OPENQASM 3.0;", @@ -177,6 +209,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", " set_frequency(predefined_frame_1, a + 4.0);", " shift_frequency(predefined_frame_1, a + 4.0);", " set_phase(predefined_frame_1, a + 4.0);", @@ -191,6 +224,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " play(predefined_frame_2, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", " psb[1] = capture_v0(predefined_frame_2);", "}", ] @@ -209,6 +243,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", " set_frequency(predefined_frame_1, 5.0);", " shift_frequency(predefined_frame_1, 5.0);", " set_phase(predefined_frame_1, 5.0);", @@ -223,6 +258,7 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined " play(predefined_frame_2, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", " psb[1] = capture_v0(predefined_frame_2);", "}", ] @@ -302,6 +338,16 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): predefined_frame_2, ArbitraryWaveform([complex(1, 0.4), 0, 0.3, complex(0.1, 0.2)], id="arb_wf"), ) + .play( + predefined_frame_1, + ErfSquareWaveform( + length=32e-9, + width=20e-9, + sigma=2e-9, + off_center=8e-9, + id="erf_square_wf", + ), + ) .capture_v0(predefined_frame_2) ) expected_str = "\n".join( @@ -313,6 +359,7 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(32.0ns, 20.0ns, 2.0ns, 8.0ns, 1, false);", " set_frequency(predefined_frame_1, 3000000000.0);", " shift_frequency(predefined_frame_1, 1000000000.0);", " set_phase(predefined_frame_1, -0.5);", @@ -328,6 +375,7 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): " play(predefined_frame_2, drag_gauss_wf);", " play(predefined_frame_1, constant_wf);", " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", " psb[1] = capture_v0(predefined_frame_2);", "}", ] diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 636cb27af..5654762fa 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -304,26 +304,29 @@ def test_erf_square_waveform(): length = 4e-9 width = 0.3 sigma = 0.2 + off_center = 1e-9 amplitude = 0.4 zero_at_edges = False id = "erf_square_wf" - wf = ErfSquareWaveform(length, width, sigma, amplitude, zero_at_edges, id) + wf = ErfSquareWaveform(length, width, sigma, off_center, amplitude, zero_at_edges, id) assert wf.id == id assert wf.zero_at_edges == zero_at_edges assert wf.amplitude == amplitude assert wf.width == width assert wf.sigma == sigma assert wf.length == length + assert wf.off_center == off_center def test_erf_square_waveform_repr(): length = 4e-9 width = 0.3 sigma = 0.2 + off_center = 1e-9 amplitude = 0.4 zero_at_edges = False id = "erf_square_wf" - wf = ErfSquareWaveform(length, width, sigma, amplitude, zero_at_edges, id) + wf = ErfSquareWaveform(length, width, sigma, off_center, amplitude, zero_at_edges, id) repr(wf) @@ -331,20 +334,24 @@ def test_erf_square_waveform_default_params(): length = 4e-9 width = 0.3 sigma = 0.2 - wf = ErfSquareWaveform(length, width, sigma) + off_center = 1e-9 + wf = ErfSquareWaveform(length, width, sigma, off_center) assert re.match(r"[A-Za-z]{10}", wf.id) assert wf.zero_at_edges is False assert wf.amplitude == 1 assert wf.width == width assert wf.sigma == sigma assert wf.length == length + assert wf.off_center == off_center def test_erf_square_wf_eq(): - wf = ErfSquareWaveform(4e-9, 0.3, 0.2, 0.7, True, "wf_es") - wf_2 = ErfSquareWaveform(wf.length, wf.width, wf.sigma, wf.amplitude, wf.zero_at_edges, wf.id) + wf = ErfSquareWaveform(4e-9, 0.3, 0.2, 1e-9, 0.7, True, "wf_es") + wf_2 = ErfSquareWaveform( + wf.length, wf.width, wf.sigma, wf.off_center, wf.amplitude, wf.zero_at_edges, wf.id + ) assert wf_2 == wf - for att in ["length", "width", "sigma", "amplitude", "zero_at_edges", "id"]: + for att in ["length", "width", "sigma", "off_center", "amplitude", "zero_at_edges", "id"]: wfc = deepcopy(wf_2) setattr(wfc, att, "wrong_value") assert wf != wfc @@ -355,6 +362,7 @@ def test_erf_square_wf_free_params(): FreeParameter("length_v"), FreeParameter("width_x"), FreeParameter("sigma_y"), + FreeParameter("off_center_x"), FreeParameter("amp_z"), id="erf_square_wf", ) @@ -362,20 +370,29 @@ def test_erf_square_wf_free_params(): FreeParameter("length_v"), FreeParameter("width_x"), FreeParameter("sigma_y"), + FreeParameter("off_center_x"), FreeParameter("amp_z"), ] wf_2 = wf.bind_values(length_v=0.6, width_x=0.4) - assert wf_2.parameters == [0.6, 0.4, FreeParameter("sigma_y"), FreeParameter("amp_z")] + assert wf_2.parameters == [ + 0.6, + 0.4, + FreeParameter("sigma_y"), + FreeParameter("off_center_x"), + FreeParameter("amp_z"), + ] _assert_wf_qasm( wf_2, - "waveform erf_square_wf = erf_square(600.0ms, 400.0ms, sigma_y * 1s, amp_z, false);", + "waveform erf_square_wf = erf_square(600.0ms, 400.0ms, sigma_y * 1s, off_center_x * 1s," + " amp_z, false);", ) - wf_3 = wf.bind_values(length_v=0.6, width_x=0.3, sigma_y=0.1) - assert wf_3.parameters == [0.6, 0.3, 0.1, FreeParameter("amp_z")] + wf_3 = wf.bind_values(length_v=0.6, width_x=0.3, sigma_y=0.1, off_center_x=0.05) + assert wf_3.parameters == [0.6, 0.3, 0.1, 0.05, FreeParameter("amp_z")] _assert_wf_qasm( - wf_3, "waveform erf_square_wf = erf_square(600.0ms, 300.0ms, 100.0ms, amp_z, false);" + wf_3, + "waveform erf_square_wf = erf_square(600.0ms, 300.0ms, 100.0ms, 50.0ms, amp_z, false);", ) @@ -450,6 +467,7 @@ def _assert_wf_qasm(waveform, expected_qasm): {"name": "length", "value": 6.000000000000001e-8, "type": "float"}, {"name": "width", "value": 3.000000000000000e-8, "type": "float"}, {"name": "sigma", "value": 5.000000000060144e-9, "type": "float"}, + {"name": "off_center", "value": 4.000000000000000e-9, "type": "float"}, {"name": "amplitude", "value": 0.4549282253548838, "type": "float"}, ], }, @@ -458,6 +476,7 @@ def _assert_wf_qasm(waveform, expected_qasm): length=6.000000000000001e-8, width=3.000000000000000e-8, sigma=5.000000000060144e-9, + off_center=4.000000000000000e-9, amplitude=0.4549282253548838, ), ), From 7e478ce12596f433fc0dcfa04eff569b7f72b241 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 26 Aug 2024 08:28:52 -0700 Subject: [PATCH 207/347] feat: Rigetti Ankaa (#1024) Co-authored-by: Tim --- src/braket/aws/aws_device.py | 1 + src/braket/devices/devices.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 041098f5a..598271424 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -76,6 +76,7 @@ class AwsDevice(Device): "Cz": "CZ", "Cphaseshift": "CPhaseShift", "Xy": "XY", + "Iswap": "ISwap", } def __init__( diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 86a32a142..ff2529234 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -50,6 +50,7 @@ class _Rigetti(str, Enum): _AspenM1 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-1" _AspenM2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2" AspenM3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" + Ankaa2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" class _Xanadu(str, Enum): _Borealis = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" From 4f4f3a56d6210d54fb09235b6cb13bf879132bc2 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Aug 2024 15:51:46 +0000 Subject: [PATCH 208/347] prepare release v1.86.0 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd55752ab..d443de044 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.86.0 (2024-08-26) + +### Features + + * Rigetti Ankaa + * add off_center to erf_square + ## v1.85.0 (2024-08-20) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7ae9064d1..09c7a8289 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.85.1.dev0" +__version__ = "1.86.0" From 93f450b23f36b1455cad39543f6d4dda45284941 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Aug 2024 15:51:46 +0000 Subject: [PATCH 209/347] update development version to v1.86.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 09c7a8289..518841c5e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.86.0" +__version__ = "1.86.1.dev0" From 8f4e88f3a6389306fdfb6f64866b0eea9ebe8db4 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 28 Aug 2024 12:16:04 -0700 Subject: [PATCH 210/347] fix: Use observable targets for targetless results (#1025) --- src/braket/circuits/result_type.py | 42 +++++++++---------- src/braket/circuits/result_types.py | 8 +--- .../braket/circuits/test_result_types.py | 19 +++++++++ 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 3e9f0dfad..a7ce2436c 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -209,32 +209,32 @@ def __init__( super().__init__(ascii_symbols) self._observable = observable self._target = QubitSet(target) - if not self._target: - if self._observable.qubit_count != 1: - raise ValueError( - f"Observable {self._observable} must only operate on 1 qubit for target=None" - ) - elif isinstance(observable, Sum): # nested target - if len(target) != len(observable.summands): - raise ValueError( - "Sum observable's target shape must be a nested list where each term's " - "target length is equal to the observable term's qubits count." - ) - self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(self._target, observable.summands): - if obs.qubit_count != len(term_target): + if self._target: + if isinstance(observable, Sum): # nested target + if len(target) != len(observable.summands): raise ValueError( "Sum observable's target shape must be a nested list where each term's " "target length is equal to the observable term's qubits count." ) - elif self._observable.qubit_count != len(self._target): - raise ValueError( - f"Observable's qubit count {self._observable.qubit_count} and " - f"the size of the target qubit set {self._target} must be equal" - ) - elif self._observable.qubit_count != len(self.ascii_symbols): + self._target = [QubitSet(term_target) for term_target in target] + for term_target, obs in zip(self._target, observable.summands): + if obs.qubit_count != len(term_target): + raise ValueError( + "Sum observable's target shape must be a nested list where each term's " + "target length is equal to the observable term's qubits count." + ) + elif self._observable.qubit_count != len(self._target): + raise ValueError( + f"Observable's qubit count {self._observable.qubit_count} and " + f"the size of the target qubit set {self._target} must be equal" + ) + elif self._observable.qubit_count != len(self.ascii_symbols): + raise ValueError( + "Observable's qubit count and the number of ASCII symbols must be equal" + ) + elif (not self._observable.targets) and self._observable.qubit_count != 1: raise ValueError( - "Observable's qubit count and the number of ASCII symbols must be equal" + f"Observable {self._observable} must only operate on 1 qubit for target=None" ) @property diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index f682b9ba1..8af36ab4a 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -14,14 +14,12 @@ from __future__ import annotations import re -from functools import reduce from typing import Union import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.free_parameter import FreeParameter from braket.circuits.observable import Observable -from braket.circuits.observables import Sum from braket.circuits.result_type import ( ObservableParameterResultType, ObservableResultType, @@ -210,11 +208,7 @@ def __init__( >>> parameters=["alpha", "beta"], >>> ) """ - if isinstance(observable, Sum): - target_qubits = reduce(QubitSet.union, map(QubitSet, target), QubitSet()) - else: - target_qubits = QubitSet(target) - + target_qubits = QubitSet(target if target is not None else observable.targets) super().__init__( ascii_symbols=[f"AdjointGradient({observable.ascii_symbols[0]})"] * len(target_qubits), observable=observable, diff --git a/test/unit_tests/braket/circuits/test_result_types.py b/test/unit_tests/braket/circuits/test_result_types.py index 5f76eeaf9..422fed7a4 100644 --- a/test/unit_tests/braket/circuits/test_result_types.py +++ b/test/unit_tests/braket/circuits/test_result_types.py @@ -272,6 +272,25 @@ def test_ir_result_level(testclass, subroutine_name, irclass, input, ir_input): "#pragma braket result adjoint_gradient expectation(hermitian([[1+0im, 0im], " "[0im, 1+0im]]) q[0]) all", ), + ( + ResultType.AdjointGradient( + Observable.H(0) @ Observable.I(1) + 2 * Observable.Z(2), + parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(h(q[0]) @ i(q[1]) + 2 * z(q[2])) " + "alpha, beta, gamma", + ), + ( + ResultType.AdjointGradient( + Observable.H(0) @ Observable.I(1) + 2 * Observable.Z(2), + target=[[3, 4], [5]], + parameters=[FreeParameter("alpha"), "beta", FreeParameter("gamma")], + ), + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "#pragma braket result adjoint_gradient expectation(h(q[3]) @ i(q[4]) + 2 * z(q[5])) " + "alpha, beta, gamma", + ), ], ) def test_result_to_ir_openqasm(result_type, serialization_properties, expected_ir): From 6cad46ee5a783ae9030917aebe016056328f10f0 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Aug 2024 16:15:40 +0000 Subject: [PATCH 211/347] prepare release v1.86.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d443de044..3fc1c64ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.86.1 (2024-08-29) + +### Bug Fixes and Other Changes + + * Use observable targets for targetless results + ## v1.86.0 (2024-08-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 518841c5e..025231973 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.86.1.dev0" +__version__ = "1.86.1" From cbe7fc9809bce43ca2e98d0daabe7126aae41f20 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 29 Aug 2024 16:15:40 +0000 Subject: [PATCH 212/347] update development version to v1.86.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 025231973..a660f1729 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.86.1" +__version__ = "1.86.2.dev0" From 2d57c5ff9a1e92e1e4198903f499381eb8b3c52d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Tue, 3 Sep 2024 16:45:34 -0700 Subject: [PATCH 213/347] change: Return observable target if absent for RT (#1026) --- src/braket/circuits/circuit.py | 4 ++-- src/braket/circuits/observable.py | 12 +++++------- src/braket/circuits/observables.py | 12 ++++++------ src/braket/circuits/result_type.py | 2 +- src/braket/circuits/result_types.py | 8 ++++---- test/unit_tests/braket/circuits/test_result_type.py | 10 ++++++++++ 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 9ce7df7f8..02d419cff 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -319,8 +319,8 @@ def add_result_type( observable = Circuit._extract_observable(result_type_to_add) # We can skip this for now for AdjointGradient (the only subtype of this # type) because AdjointGradient can only be used when `shots=0`, and the - # qubit_observable_mapping is used to generate basis rotation instrunctions - # and make sure the observables are simultaneously commuting for `shots>0` mode. + # qubit_observable_mapping is used to generate basis rotation instructions + # and make sure the observables mutually commute for `shots>0` mode. supports_basis_rotation_instructions = not isinstance( result_type_to_add, ObservableParameterResultType ) diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 11ed02eb6..e0572e3d9 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -40,20 +40,18 @@ def __init__( self, qubit_count: int, ascii_symbols: Sequence[str], targets: QubitSetInput | None = None ): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) - if targets is not None: - targets = QubitSet(targets) + targets = QubitSet(targets) + if targets: if (num_targets := len(targets)) != qubit_count: raise ValueError( f"Length of target {num_targets} does not match qubit count {qubit_count}" ) - self._targets = targets - else: - self._targets = None + self._targets = targets self._coef = 1 def _unscaled(self) -> Observable: return Observable( - qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols, targets=self.targets + qubit_count=self.qubit_count, ascii_symbols=self.ascii_symbols, targets=self._targets ) def to_ir( @@ -207,7 +205,7 @@ def __sub__(self, other: Observable): def __repr__(self) -> str: return ( f"{self.name}('qubit_count': {self._qubit_count})" - if self._targets is None + if not self._targets else f"{self.name}('qubit_count': {self._qubit_count}, 'target': {self._targets})" ) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 9346435a2..8249d156f 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -322,9 +322,9 @@ def __init__(self, observables: list[Observable]): f"{'@'.join([obs.ascii_symbols[0] for obs in unscaled_factors])}" ) all_targets = [factor.targets for factor in unscaled_factors] - if all(targets is None for targets in all_targets): - merged_targets = None - elif all(targets is not None for targets in all_targets): + if not any(all_targets): + merged_targets = QubitSet() + elif all(all_targets): flat_targets = [qubit for target in all_targets for qubit in target] merged_targets = QubitSet(flat_targets) if len(merged_targets) != len(flat_targets): @@ -508,9 +508,9 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni self._summands = tuple(flattened_observables) qubit_count = max(flattened_observables, key=lambda obs: obs.qubit_count).qubit_count all_targets = [observable.targets for observable in flattened_observables] - if all(targets is None for targets in all_targets): - targets = None - elif all(targets is not None for targets in all_targets): + if not any(all_targets): + targets = QubitSet() + elif all(all_targets): targets = all_targets else: raise ValueError("Cannot mix terms with and without targets") diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index a7ce2436c..8343429e0 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -243,7 +243,7 @@ def observable(self) -> Observable: @property def target(self) -> QubitSet: - return self._target + return self._target or self._observable.targets @target.setter def target(self, target: QubitSetInput) -> None: diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 8af36ab4a..6c1232b06 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -218,7 +218,7 @@ def __init__( def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( - target=self.target, + target=self._target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) @@ -477,7 +477,7 @@ def _to_jaqcd(self) -> ir.Expectation: def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( - target=self.target, + target=self._target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) @@ -552,7 +552,7 @@ def _to_jaqcd(self) -> ir.Sample: def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( - target=self.target, + target=self._target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) @@ -632,7 +632,7 @@ def _to_jaqcd(self) -> ir.Variance: def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( - target=self.target, + target=self._target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index bc7cc0909..bb543b7c0 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -18,6 +18,7 @@ from braket.circuits.free_parameter import FreeParameter from braket.circuits.result_type import ObservableParameterResultType from braket.circuits.serialization import IRType +from braket.registers import QubitSet @pytest.fixture @@ -168,6 +169,15 @@ def test_obs_rt_repr(): ) +def test_obs_rt_target(): + assert ObservableResultType( + ascii_symbols=["Obs"], observable=Observable.X(), target=1 + ).target == QubitSet(1) + assert ObservableResultType( + ascii_symbols=["Obs"], observable=Observable.X(1) + ).target == QubitSet(1) + + @pytest.mark.parametrize( "ir_type, serialization_properties, expected_exception, expected_message", [ From 1f6bf0fc83040da58dae1f242a29fc9acf92ab61 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 4 Sep 2024 14:45:52 -0700 Subject: [PATCH 214/347] deprecation: Retire IonQ Harmony (#1030) --- src/braket/devices/devices.py | 2 +- test/integ_tests/test_device_creation.py | 2 +- test/integ_tests/test_measure.py | 2 +- test/integ_tests/test_reservation_arn.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index ff2529234..0af058d42 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -31,7 +31,7 @@ class _IQM(str, Enum): Garnet = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" class _IonQ(str, Enum): - Harmony = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" + _Harmony = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" Aria1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" Forte1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Forte-1" diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index c7183c3e6..2b718c211 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -18,7 +18,7 @@ from braket.devices import Devices RIGETTI_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" -IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" +IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" IQM_ARN = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py index 9c8fdc02c..8fd72e6bf 100644 --- a/test/integ_tests/test_measure.py +++ b/test/integ_tests/test_measure.py @@ -23,7 +23,7 @@ DEVICE = LocalSimulator() SHOTS = 8000 -IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" +IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" IQM_ARN = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index 64135f76e..57833a840 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -33,7 +33,7 @@ def reservation_arn(aws_session): def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): circuit = Circuit().h(0) - device = AwsDevice(Devices.IonQ.Harmony) + device = AwsDevice(Devices.IonQ.Aria1) with pytest.raises(ClientError, match="Reservation arn is invalid"): device.run(circuit, shots=10, reservation_arn=reservation_arn) From 6236d632560c87cd19f5bda511c5035f07601c05 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 5 Sep 2024 18:38:00 +0000 Subject: [PATCH 215/347] prepare release v1.87.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc1c64ef..18bde840f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.87.0 (2024-09-05) + +### Deprecations and Removals + + * Retire IonQ Harmony + +### Bug Fixes and Other Changes + + * Return observable target if absent for RT + ## v1.86.1 (2024-08-29) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a660f1729..7f61b52dd 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.86.2.dev0" +__version__ = "1.87.0" From 42d0ef8fc275b0dfbb560ac573846192a20e1587 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 5 Sep 2024 18:38:00 +0000 Subject: [PATCH 216/347] update development version to v1.87.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7f61b52dd..2deedb28a 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.87.0" +__version__ = "1.87.1.dev0" From 2ddb3d2c1663befe4f004020ab50e6514efdba8d Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:29:30 -0700 Subject: [PATCH 217/347] infra: update to cloudpickle 3.x (#1032) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bf429b266..45cc590bd 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ "backoff", "boltons", "boto3>=1.28.53", - "cloudpickle==2.2.1", + "cloudpickle>=3", "nest-asyncio", "networkx", "numpy", From fd597d6d1fe2f38dc14e7e2fbf6a72657cf0f3b5 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:43:07 -0400 Subject: [PATCH 218/347] fix: Pass through inputs for SerializableProgram simulation (#1033) --- src/braket/devices/local_simulator.py | 5 +++-- .../braket/devices/test_local_simulator.py | 21 +++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 02b379596..f9e70a1ea 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -290,8 +290,9 @@ def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots return program @_construct_payload.register - def _(self, program: SerializableProgram, _inputs, _shots): - return OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM)) + def _(self, program: SerializableProgram, inputs: Optional[dict[str, float]], _shots): + inputs_copy = inputs.copy() if inputs is not None else {} + return OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM), inputs=inputs_copy) @_construct_payload.register def _(self, program: AnalogHamiltonianSimulation, _inputs, _shots): diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 451553f02..6c2976812 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -588,10 +588,8 @@ def test_run_serializable_program_model(): source=""" qubit[2] q; bit[2] c; - h q[0]; cnot q[0], q[1]; - c = measure q; """ ) @@ -599,6 +597,25 @@ def test_run_serializable_program_model(): assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) +def test_run_serializable_program_model_with_inputs(): + dummy = DummySerializableProgramSimulator() + sim = LocalSimulator(dummy) + task = sim.run( + DummySerializableProgram( + source=""" +input float a; +qubit[2] q; +bit[2] c; +h q[0]; +cnot q[0], q[1]; +c = measure q; +""" + ), + inputs={"a": 0.1}, + ) + assert task.result() == GateModelQuantumTaskResult.from_object(GATE_MODEL_RESULT) + + @pytest.mark.xfail(raises=ValueError) def test_run_gate_model_value_error(): dummy = DummyCircuitSimulator() From d3c0ec262e1667d7d90638401d2070b0c85700b1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Sep 2024 21:12:42 +0000 Subject: [PATCH 219/347] prepare release v1.87.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18bde840f..1a7be1b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.87.1 (2024-09-23) + +### Bug Fixes and Other Changes + + * Pass through inputs for SerializableProgram simulation + ## v1.87.0 (2024-09-05) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2deedb28a..f716aaca9 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.87.1.dev0" +__version__ = "1.87.1" From d45958b774757274f2cb6008eaf049bf1ee1d644 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 23 Sep 2024 21:12:42 +0000 Subject: [PATCH 220/347] update development version to v1.87.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f716aaca9..33c9067cd 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.87.1" +__version__ = "1.87.2.dev0" From b076fdad598a7603f66778528645715566711773 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:12:04 -0400 Subject: [PATCH 221/347] deprecation: Mark Aspen-M-3 as deprecated, replace with Ankaa-2 in tests (#1034) --- README.md | 2 +- src/braket/devices/devices.py | 2 +- test/integ_tests/test_cost_tracking.py | 2 +- test/integ_tests/test_device_creation.py | 4 ++-- test/integ_tests/test_pulse.py | 2 +- test/unit_tests/braket/aws/common_test_utils.py | 2 +- test/unit_tests/braket/aws/test_aws_device.py | 4 ++-- test/unit_tests/braket/aws/test_aws_quantum_job.py | 2 +- test/unit_tests/braket/jobs/test_quantum_job_creation.py | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 415a18c25..3c3e85d51 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ import boto3 from braket.circuits import Circuit from braket.aws import AwsDevice -device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") +device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Ankaa-2") bell = Circuit().h(0).cnot(0, 1) task = device.run(bell) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 0af058d42..44d6edb13 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -49,7 +49,7 @@ class _Rigetti(str, Enum): _Aspen11 = "arn:aws:braket:::device/qpu/rigetti/Aspen-11" _AspenM1 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-1" _AspenM2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2" - AspenM3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" + _AspenM3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" Ankaa2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" class _Xanadu(str, Enum): diff --git a/test/integ_tests/test_cost_tracking.py b/test/integ_tests/test_cost_tracking.py index f493c9779..c2c590331 100644 --- a/test/integ_tests/test_cost_tracking.py +++ b/test/integ_tests/test_cost_tracking.py @@ -29,7 +29,7 @@ [ "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony", "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet", - "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3", + "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2", ], ) def test_qpu_tracking(qpu): diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 2b718c211..58bdb35a1 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -17,11 +17,11 @@ from braket.aws import AwsDevice from braket.devices import Devices -RIGETTI_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" +RIGETTI_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" IQM_ARN = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" -PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" +PULSE_ARN = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" @pytest.mark.parametrize( diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 4eb3ffa93..5f6c7b5d1 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -11,7 +11,7 @@ @pytest.fixture def device(): - return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3") + return AwsDevice("arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2") @pytest.fixture diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index aaca559f5..0e1fd4d48 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -17,7 +17,7 @@ from braket.aws import AwsQuantumTaskBatch DWAVE_ARN = "arn:aws:braket:::device/qpu/d-wave/Advantage_system1" -RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Aspen-10" +RIGETTI_ARN = "arn:aws:braket:::device/qpu/rigetti/Ankaa-2" IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" SV1_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index a778dfb5f..3fed52aa2 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -216,7 +216,7 @@ def test_mock_rigetti_schema_1(): MOCK_GATE_MODEL_QPU_1 = { - "deviceName": "Aspen-10", + "deviceName": "Ankaa-2", "deviceType": "QPU", "providerName": "Rigetti", "deviceStatus": "OFFLINE", @@ -1451,7 +1451,7 @@ def test_run_device_poll_interval_kwargs( capabilities = MOCK_GATE_MODEL_QPU_CAPABILITIES_1 capabilities.service.getTaskPollIntervalMillis = poll_interval_seconds properties = { - "deviceName": "Aspen-10", + "deviceName": "Ankaa-2", "deviceType": "QPU", "providerName": "provider1", "deviceStatus": "OFFLINE", diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 67ca98228..028ad3cc4 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -70,7 +70,7 @@ def _get_job_response(**kwargs): }, "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), "deviceConfig": { - "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", + "device": "arn:aws:braket:::device/qpu/rigetti/Ankaa-2", }, "hyperParameters": { "foo": "bar", diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py index d12a29b00..39cb94c5d 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -198,7 +198,7 @@ def _get_job_response(**kwargs): }, "createdAt": datetime.datetime(2021, 6, 28, 21, 4, 51), "deviceConfig": { - "device": "arn:aws:braket:::device/qpu/rigetti/Aspen-10", + "device": "arn:aws:braket:::device/qpu/rigetti/Ankaa-2", }, "hyperParameters": { "foo": "bar", From 71dc33b2769f547690c7ef6962858eb3b917fa6d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:32:38 -0400 Subject: [PATCH 222/347] fix: Update pulse integration tests for Ankaa-2 device (#1035) --- test/integ_tests/test_pulse.py | 63 +++++++++++++++------------------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 5f6c7b5d1..e5f42bcd3 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -174,7 +174,7 @@ def h_gate(q0): return Circuit().rz(q0, np.pi).rx(q0, np.pi / 2).rz(q0, np.pi / 2).rx(q0, -np.pi / 2) -def cz_pulse( +def make_pulse( q0: str, q1: str, shift_phases_q0: float, @@ -182,30 +182,22 @@ def cz_pulse( waveform: ArbitraryWaveform, device: AwsDevice, ): - q0_rf_frame = device.frames[f"q{q0}_rf_frame"] - q1_rf_frame = device.frames[f"q{q1}_rf_frame"] - q0_q1_cz_frame = device.frames[f"q{q0}_q{q1}_cz_frame"] - frames = [q0_rf_frame, q1_rf_frame, q0_q1_cz_frame] + q0_drive_frame = device.frames[f"Transmon_{q0}_charge_tx"] + q1_drive_frame = device.frames[f"Transmon_{q1}_charge_tx"] + frames = [q0_drive_frame, q1_drive_frame] - dt = device.properties.pulse.ports[q0_q1_cz_frame.port.id].dt + dt = device.properties.pulse.ports[q0_drive_frame.port.id].dt wfm_duration = len(waveform.amplitudes) * dt pulse_sequence = ( PulseSequence() .barrier(frames) - .play(q0_q1_cz_frame, waveform) - .delay(q0_rf_frame, wfm_duration) - .shift_phase(q0_rf_frame, shift_phases_q0) - .delay(q1_rf_frame, wfm_duration) - .shift_phase(q1_rf_frame, shift_phases_q1) + .delay(q0_drive_frame, wfm_duration) + .shift_phase(q0_drive_frame, shift_phases_q0) + .delay(q1_drive_frame, wfm_duration) + .shift_phase(q1_drive_frame, shift_phases_q1) .barrier(frames) ) - for phase, q in [(shift_phases_q0 * 0.5, q0), (-shift_phases_q1 * 0.5, q1)]: - for neighbor in device.properties.paradigm.connectivity.connectivityGraph[str(q)]: - xy_frame_name = f"q{min(q, int(neighbor))}_q{max(q, int(neighbor))}_xy_frame" - if xy_frame_name in device.frames: - xy_frame = device.frames[xy_frame_name] - pulse_sequence.shift_phase(xy_frame, phase) return pulse_sequence @@ -216,17 +208,17 @@ def test_pulse_bell(arbitrary_waveform, device): a, b, ) = ( - 10, - 113, + 26, + 33, ) # qubits used p0, p1 = 1.1733407221086924, 6.269846678712192 theta_0, theta_1 = FreeParameter("theta_0"), FreeParameter("theta_1") - a_b_cz_waveform = arbitrary_waveform - cz = cz_pulse(a, b, theta_0, theta_1, a_b_cz_waveform, device) + a_b_waveform = arbitrary_waveform + pulse = make_pulse(a, b, theta_0, theta_1, a_b_waveform, device) - bell_pair_with_gates = Circuit().h(a).h(b).cz(a, b).h(b) + bell_pair_with_gates = Circuit().h(a).h(b).iswap(a, b).h(b) bell_pair_with_pulses_unbound = ( - h_gate(a) + h_gate(b) + Circuit().pulse_gate([a, b], cz) + h_gate(b) + h_gate(a) + h_gate(b) + Circuit().pulse_gate([a, b], pulse) + h_gate(b) ) bell_pair_with_pulses = bell_pair_with_pulses_unbound(theta_0=p0, theta_1=p1) @@ -266,27 +258,27 @@ def test_pulse_sequence(arbitrary_waveform, device): a, b, ) = ( - 10, - 113, + 26, + 33, ) # qubits used p0, p1 = 1.1733407221086924, 6.269846678712192 theta_0, theta_1 = FreeParameter("theta_0"), FreeParameter("theta_1") - a_b_cz_waveform = arbitrary_waveform + a_b_waveform = arbitrary_waveform - cz_with_pulses_unbound = cz_pulse(a, b, theta_0, theta_1, a_b_cz_waveform, device) + pulse_unbound = make_pulse(a, b, theta_0, theta_1, a_b_waveform, device) - q0_readout_frame = device.frames[f"q{a}_ro_rx_frame"] - q1_readout_frame = device.frames[f"q{b}_ro_rx_frame"] - cz_with_pulses = ( - cz_with_pulses_unbound(theta_0=p0, theta_1=p1) + q0_readout_frame = device.frames[f"Transmon_{a}_readout_rx"] + q1_readout_frame = device.frames[f"Transmon_{b}_readout_rx"] + pulses = ( + pulse_unbound(theta_0=p0, theta_1=p1) .capture_v0(q0_readout_frame) .capture_v0(q1_readout_frame) ) - cz_with_gates = Circuit().cz(a, b) + circuit_with_gates = Circuit().iswap(a, b) num_shots = 1000 - gate_task = device.run(cz_with_gates, shots=num_shots, disable_qubit_rewiring=True) - pulse_task = device.run(cz_with_pulses, shots=num_shots) + gate_task = device.run(circuit_with_gates, shots=num_shots, disable_qubit_rewiring=True) + pulse_task = device.run(pulses, shots=num_shots) if not device.is_available: try: @@ -313,12 +305,13 @@ def test_pulse_sequence(arbitrary_waveform, device): assert chi_squared < 10 # adjust this threshold if test is flaky +@pytest.mark.skip(reason="needs to be updated to work correctly on Ankaa-2") def test_gate_calibration_run(device, pulse_sequence): if device.status == "OFFLINE": pytest.skip("Device offline") user_gate_calibrations = GateCalibrations({(Gate.Rx(math.pi / 2), QubitSet(0)): pulse_sequence}) num_shots = 50 - bell_circuit = Circuit().rx(0, math.pi / 2).rx(1, math.pi / 2).cz(0, 1).rx(1, -math.pi / 2) + bell_circuit = Circuit().rx(0, math.pi / 2).rx(1, math.pi / 2).iswap(0, 1).rx(1, -math.pi / 2) user_calibration_task = device.run( bell_circuit, gate_definitions=user_gate_calibrations.pulse_sequences, From 5870ea64d8f5785207bda300b037f98703f2696e Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 27 Sep 2024 20:47:30 +0000 Subject: [PATCH 223/347] prepare release v1.88.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7be1b4f..e23ddae20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.88.0 (2024-09-27) + +### Deprecations and Removals + + * Mark Aspen-M-3 as deprecated, replace with Ankaa-2 in tests + +### Bug Fixes and Other Changes + + * Update pulse integration tests for Ankaa-2 device + ## v1.87.1 (2024-09-23) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 33c9067cd..4c391e2c1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.87.2.dev0" +__version__ = "1.88.0" From 43d0c7ebe64a9871416bcac16fbb6085ca4beb7d Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 27 Sep 2024 20:47:30 +0000 Subject: [PATCH 224/347] update development version to v1.88.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4c391e2c1..a4bd9b268 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.0" +__version__ = "1.88.1.dev0" From 5534034b3091d221b338b044f55610266b020fb3 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Sun, 6 Oct 2024 14:52:04 -0700 Subject: [PATCH 225/347] =?UTF-8?q?infra:=20remove=20doc=20dependencies=20?= =?UTF-8?q?from=20the=20test=20list=20and=20add=20to=20their=20ow=E2=80=A6?= =?UTF-8?q?=20(#1037)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Coull --- setup.py | 6 ++++-- tox.ini | 4 +--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 45cc590bd..18bc591e1 100644 --- a/setup.py +++ b/setup.py @@ -55,11 +55,13 @@ "pytest-cov", "pytest-rerunfailures", "pytest-xdist[psutil]", + "tox", + ], + "docs": [ "sphinx", "sphinx-rtd-theme", "sphinxcontrib-apidoc", - "tox", - ] + ], }, include_package_data=True, url="https://github.com/amazon-braket/amazon-braket-sdk-python", diff --git a/tox.ini b/tox.ini index 95c862106..f8da75234 100644 --- a/tox.ini +++ b/tox.ini @@ -107,11 +107,9 @@ commands = basepython = python3 deps = {[test-deps]deps} - sphinx - sphinx-rtd-theme - sphinxcontrib-apidoc commands = sphinx-build -E -T -b html doc build/documentation/html -j auto +extras = docs [testenv:serve-docs] basepython = python3 From 36e32f63d33a97b8b17addb0e90b692cda93b4ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 13:07:00 -0400 Subject: [PATCH 226/347] infra: bump pypa/gh-action-pypi-publish from 1.9.0 to 1.10.0 (#1027) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 942974db7..00d746acb 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 # release/v1 + uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 # release/v1 with: password: ${{ secrets.pypi_token }} From cc3698cc6d92e2fda18f6be951cabea5dc3fb6bf Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 18 Oct 2024 10:48:49 -0700 Subject: [PATCH 227/347] fix: correct typing for task results methods (#1039) --- .coveragerc | 5 ++++ src/braket/aws/aws_quantum_task_batch.py | 29 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/.coveragerc b/.coveragerc index fe9662c0a..22cb4caeb 100644 --- a/.coveragerc +++ b/.coveragerc @@ -32,6 +32,11 @@ exclude_lines = # Avoid situation where system version causes coverage issues if sys.version_info.minor == 9: + # Avoid type checking import conditionals + if TYPE_CHECKING: + + + [html] directory = build/coverage diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 300963a6f..30c3a8658 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -16,7 +16,7 @@ import time from concurrent.futures.thread import ThreadPoolExecutor from itertools import repeat -from typing import Any, Union +from typing import TYPE_CHECKING, Any, Union from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem @@ -30,6 +30,14 @@ from braket.registers.qubit_set import QubitSet from braket.tasks.quantum_task_batch import QuantumTaskBatch +if TYPE_CHECKING: + from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( + AnalogHamiltonianSimulationQuantumTaskResult, + ) + from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult + from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult + from braket.tasks.photonic_model_quantum_task_result import PhotonicModelQuantumTaskResult + class AwsQuantumTaskBatch(QuantumTaskBatch): """Executes a batch of quantum tasks in parallel. @@ -331,7 +339,12 @@ def results( fail_unsuccessful: bool = False, max_retries: int = MAX_RETRIES, use_cached_value: bool = True, - ) -> list[AwsQuantumTask]: + ) -> list[ + GateModelQuantumTaskResult + | AnnealingQuantumTaskResult + | PhotonicModelQuantumTaskResult + | AnalogHamiltonianSimulationQuantumTaskResult + ]: """Retrieves the result of every quantum task in the batch. Polling for results happens in parallel; this method returns when all quantum tasks @@ -348,7 +361,8 @@ def results( even when results have already been cached. Default: `True`. Returns: - list[AwsQuantumTask]: The results of all of the quantum tasks in the batch. + list[GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult | AnalogHamiltonianSimulationQuantumTaskResult]: The # noqa: E501 + results of all of the quantum tasks in the batch. `FAILED`, `CANCELLED`, or timed out quantum tasks will have a result of None """ if not self._results or not use_cached_value: @@ -369,7 +383,14 @@ def results( return self._results @staticmethod - def _retrieve_results(tasks: list[AwsQuantumTask], max_workers: int) -> list[AwsQuantumTask]: + def _retrieve_results( + tasks: list[AwsQuantumTask], max_workers: int + ) -> list[ + GateModelQuantumTaskResult + | AnnealingQuantumTaskResult + | PhotonicModelQuantumTaskResult + | AnalogHamiltonianSimulationQuantumTaskResult + ]: with ThreadPoolExecutor(max_workers=max_workers) as executor: result_futures = [executor.submit(task.result) for task in tasks] return [future.result() for future in result_futures] From 447c87d2fa648937bc8331f790a0b9fc6959e520 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Oct 2024 16:14:56 +0000 Subject: [PATCH 228/347] prepare release v1.88.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e23ddae20..34565b899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.88.1 (2024-10-21) + +### Bug Fixes and Other Changes + + * correct typing for task results methods + ## v1.88.0 (2024-09-27) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a4bd9b268..0020ca15e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.1.dev0" +__version__ = "1.88.1" From 90ca19645e91d949d9c38c133d4a899118384c25 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Oct 2024 16:14:56 +0000 Subject: [PATCH 229/347] update development version to v1.88.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0020ca15e..86ce67991 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.1" +__version__ = "1.88.2.dev0" From a7348c681bc97e10c14edf69196f06b8d010e8d4 Mon Sep 17 00:00:00 2001 From: Jeff Heckey Date: Mon, 18 Nov 2024 12:55:34 -0800 Subject: [PATCH 230/347] fix: Pin cloudpickle==2.2.1 (#1044) --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 18bc591e1..2e1afe1c7 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,9 @@ "backoff", "boltons", "boto3>=1.28.53", - "cloudpickle>=3", + # SageMaker pinned cloudpickle==2.2.1 + # see https://github.com/aws/sagemaker-python-sdk/issues/4871 + "cloudpickle==2.2.1", "nest-asyncio", "networkx", "numpy", From 0644b0a9e79d0d13eb8bb6aff5be4a18216b529d Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Nov 2024 21:13:46 +0000 Subject: [PATCH 231/347] prepare release v1.88.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34565b899..7f7e8e7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.88.2 (2024-11-18) + +### Bug Fixes and Other Changes + + * Pin cloudpickle==2.2.1 + ## v1.88.1 (2024-10-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 86ce67991..987a33464 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.2.dev0" +__version__ = "1.88.2" From e6775d54f45917c8b28919cbea84bf2c50b103c8 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Nov 2024 21:13:46 +0000 Subject: [PATCH 232/347] update development version to v1.88.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 987a33464..3a2292d9b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.2" +__version__ = "1.88.3.dev0" From 5cb4a31759f13cd724610b7fd8e156086577db14 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:25:16 -0800 Subject: [PATCH 233/347] doc: add dual navigation buttons and cleanup some docstrings (#758) --- doc/conf.py | 7 ++++++- src/braket/circuits/circuit.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 8a16ca231..b5eae7acc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -19,18 +19,23 @@ ] source_suffix = ".rst" -master_doc = "index" +root_doc = "index" autoclass_content = "both" autodoc_member_order = "bysource" default_role = "py:obj" html_theme = "sphinx_rtd_theme" +html_theme_options = { + "prev_next_buttons_location": "both", +} htmlhelp_basename = f"{project}doc" language = "en" napoleon_use_rtype = False +napoleon_google_docstring = True +napoleon_numpy_docstring = False apidoc_module_dir = "../src/braket" apidoc_output_dir = "_apidoc" diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 02d419cff..e3181a603 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1537,9 +1537,9 @@ def to_unitary(self) -> np.ndarray: `qubit count` > 10. Returns: - np.ndarray: A numpy array with shape (2^qubit_count, 2^qubit_count) representing the - circuit as a unitary. For an empty circuit, an empty numpy array is returned - (`array([], dtype=complex)`) + np.ndarray: A numpy array with shape (2 :sup:`qubit_count`, 2 :sup:`qubit_count`) + representing the circuit as a unitary. For an empty circuit, an empty numpy array + is returned (`array([], dtype=complex)`) Raises: TypeError: If circuit is not composed only of `Gate` instances, From cfe02145a2a88b9433bda4588eeb98897ef4fc93 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Nov 2024 16:14:20 +0000 Subject: [PATCH 234/347] prepare release v1.88.2.post0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f7e8e7db..78fe9797d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.88.2.post0 (2024-11-25) + +### Documentation Changes + + * add dual navigation buttons and cleanup some docstrings + ## v1.88.2 (2024-11-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3a2292d9b..997180b39 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.3.dev0" +__version__ = "1.88.2.post0" From b378184d99350c9447377b8fab3410fe062aba91 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Nov 2024 16:14:20 +0000 Subject: [PATCH 235/347] update development version to v1.88.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 997180b39..3a2292d9b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.2.post0" +__version__ = "1.88.3.dev0" From 7966a3d3228d25fc9b6380b69639f6960fec70aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 12:39:20 -0800 Subject: [PATCH 236/347] infra: bump pypa/gh-action-pypi-publish from 1.10.0 to 1.11.0 (#1042) --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 00d746acb..cfd03b7a4 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 # release/v1 + uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # release/v1 with: password: ${{ secrets.pypi_token }} From 509bad6ac813a965d59cc03475f0e5a3424ac65b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:35:34 -0500 Subject: [PATCH 237/347] infra: bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2 (#1047) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index cfd03b7a4..20acaf742 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # release/v1 + uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # release/v1 with: password: ${{ secrets.pypi_token }} From daab5540821c71b17f124817ac7e9e9188a0feae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:12:02 -0500 Subject: [PATCH 238/347] infra: bump codecov/codecov-action from 4.5.0 to 5.0.7 (#1045) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8ce80816f..79430846b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 + uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 2e8a1be6b96ef749a99fb8dca3becd3453cd0f98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:18:31 -0500 Subject: [PATCH 239/347] infra: bump thehanimo/pr-title-checker from 1.4.2 to 1.4.3 (#1046) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/pr-title-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-title-checker.yml b/.github/workflows/pr-title-checker.yml index 78126bdce..a0f103127 100644 --- a/.github/workflows/pr-title-checker.yml +++ b/.github/workflows/pr-title-checker.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check PR Title" - uses: thehanimo/pr-title-checker@v1.4.2 + uses: thehanimo/pr-title-checker@v1.4.3 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pass_on_octokit_error: false From 13fef326a46641d22f410c4d91683e05bee2174b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:46:22 -0500 Subject: [PATCH 240/347] infra: bump actions/checkout from 4.1.7 to 4.2.0 (#1036) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jacob Feldman Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index e047484ad..de07350a7 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -16,7 +16,7 @@ jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index ab2656bc0..e386207fc 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -21,7 +21,7 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 20acaf742..9e74d72bc 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,7 +12,7 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 79430846b..5556836de 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 3b439db6d..2e5def62c 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -14,7 +14,7 @@ jobs: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: From aead183e7c133a7cf1079aa931dc6aad4d1ae7a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 10:58:50 -0500 Subject: [PATCH 241/347] infra: bump actions/setup-python from 5.1.0 to 5.2.0 (#1028) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index de07350a7..cf5f05bae 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index e386207fc..f3cac948c 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 9e74d72bc..334a2b5b5 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 5556836de..9d06d53c0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 2e5def62c..8fbb96e09 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.x' - name: Install wheel From 73a03c6ab70b5109b2d8dffd403a9decd206cb72 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:41:10 -0800 Subject: [PATCH 242/347] =?UTF-8?q?fix:=20increase=20timeout=20for=20tasks?= =?UTF-8?q?=20to=20avoid=20async=20polling=20timeout=20during=E2=80=A6=20(?= =?UTF-8?q?#1048)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/unit_tests/braket/aws/test_aws_quantum_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 28032d943..51fe2c75f 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -91,7 +91,7 @@ def quantum_task_quiet(aws_session): @pytest.fixture def circuit_task(aws_session): - return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=2) + return AwsQuantumTask("foo:bar:arn", aws_session, poll_timeout_seconds=4) @pytest.fixture From 77353c14f68a651aeb8028187de928f0c1071fff Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 6 Dec 2024 19:02:26 +0000 Subject: [PATCH 243/347] prepare release v1.88.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78fe9797d..37644d324 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.88.3 (2024-12-06) + +### Bug Fixes and Other Changes + + * increase timeout for tasks to avoid async polling timeout during… + ## v1.88.2.post0 (2024-11-25) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3a2292d9b..e73577fd4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.3.dev0" +__version__ = "1.88.3" From 8367e48ada16ae060ebf7f6420d6b568ebe30d36 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 6 Dec 2024 19:02:26 +0000 Subject: [PATCH 244/347] update development version to v1.88.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e73577fd4..5dee44e67 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.3" +__version__ = "1.88.4.dev0" From 880e9212b8bcc835c3a6922750ea169e2883c8b6 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:59:17 -0500 Subject: [PATCH 245/347] infra: constrain schemas to >= 1.23.0 (#1053) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2e1afe1c7..d57ddde28 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.21.3", + "amazon-braket-schemas>=1.23.0", "amazon-braket-default-simulator>=1.26.0", "oqpy~=0.3.5", "backoff", From ecb18aba8ffba4606b03cfc83d6f8cd8459aa941 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:25:19 -0500 Subject: [PATCH 246/347] infra: bump actions/setup-python from 5.2.0 to 5.3.0 (#1049) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index cf5f05bae..348941fa1 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index f3cac948c..76fb580a2 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 334a2b5b5..557933a55 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9d06d53c0..a53a38227 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 8fbb96e09..0f6a34bed 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set up Python - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 + uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: '3.x' - name: Install wheel From 9ee694a95583608eb605edd1a07eac15ac0a6a19 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Wed, 5 Feb 2025 11:26:36 -0500 Subject: [PATCH 247/347] infra: linter updates (#1059) --- src/braket/parametric/free_parameter.py | 2 +- test/integ_tests/test_device_creation.py | 4 ++-- test/integ_tests/test_measure.py | 2 +- test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py | 4 ++-- .../braket/circuits/test_unicode_circuit_diagram.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 8f437bbca..3ef188004 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -108,7 +108,7 @@ def __init__(self, name: str): Examples: >>> param1 = FreeParameter("theta") - >>> param1 = FreeParameter("\u03B8") + >>> param1 = FreeParameter("\u03b8") """ self._set_name(name) super().__init__(expression=self._name) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 58bdb35a1..07dce3eb8 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -37,7 +37,7 @@ def test_device_creation(arn, created_braket_devices): assert device.properties -@pytest.mark.parametrize("arn", [(PULSE_ARN)]) +@pytest.mark.parametrize("arn", [PULSE_ARN]) def test_device_pulse_properties(arn, aws_session, created_braket_devices): device = created_braket_devices[arn] assert device.ports @@ -57,7 +57,7 @@ def test_get_devices_arn(arn): assert results[0].arn == arn -@pytest.mark.parametrize("arn", [(PULSE_ARN)]) +@pytest.mark.parametrize("arn", [PULSE_ARN]) def test_device_gate_calibrations(arn, aws_session, created_braket_devices): device = created_braket_devices[arn] assert device.gate_calibrations diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py index 8fd72e6bf..c16713536 100644 --- a/test/integ_tests/test_measure.py +++ b/test/integ_tests/test_measure.py @@ -53,7 +53,7 @@ def test_measure_on_local_sim(sim): assert result.measured_qubits == [0, 1] -@pytest.mark.parametrize("arn", [(IQM_ARN)]) +@pytest.mark.parametrize("arn", [IQM_ARN]) def test_measure_on_supported_devices(arn): device = AwsDevice(arn) if not device.is_available: diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index b10891830..901bbbb9b 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -98,7 +98,7 @@ def test_one_gate_with_zero_global_phase(): def test_one_gate_one_qubit_rotation_with_unicode(): - theta = FreeParameter("\u03B8") + theta = FreeParameter("\u03b8") circ = Circuit().rx(angle=theta, target=0) # Column formats to length of the gate plus the ascii representation for the angle. expected = ( @@ -114,7 +114,7 @@ def test_one_gate_one_qubit_rotation_with_unicode(): def test_one_gate_with_parametric_expression_global_phase_(): - theta = FreeParameter("\u03B8") + theta = FreeParameter("\u03b8") circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) expected = ( "T : |0| 1 | 2 |", diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index efc87ee24..a0288ddda 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -110,7 +110,7 @@ def test_one_gate_with_zero_global_phase(): def test_one_gate_one_qubit_rotation_with_unicode(): - theta = FreeParameter("\u03B8") + theta = FreeParameter("\u03b8") circ = Circuit().rx(angle=theta, target=0) # Column formats to length of the gate plus the ascii representation for the angle. expected = ( @@ -126,7 +126,7 @@ def test_one_gate_one_qubit_rotation_with_unicode(): def test_one_gate_with_parametric_expression_global_phase_(): - theta = FreeParameter("\u03B8") + theta = FreeParameter("\u03b8") circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) expected = ( "T : │ 0 │ 1 │ 2 │", From 82ff1af0301390928870e708018ddd4b0152c073 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Wed, 5 Feb 2025 11:42:40 -0500 Subject: [PATCH 248/347] feat: support CUDA-Q decorator kernel with hybrid job decorator (#1056) --- src/braket/jobs/hybrid_job.py | 135 ++++++++++++- .../unit_tests/braket/jobs/test_hybrid_job.py | 189 +++++++++++++++++- 2 files changed, 309 insertions(+), 15 deletions(-) diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index 77f5f43d0..f3e1661a1 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -16,15 +16,17 @@ import functools import importlib.util import inspect +import os import re import shutil import sys import tempfile import warnings from collections.abc import Callable, Iterable +from contextlib import contextmanager from logging import Logger, getLogger from pathlib import Path -from types import ModuleType +from types import CodeType, ModuleType from typing import Any import cloudpickle @@ -39,9 +41,14 @@ StoppingCondition, ) from braket.jobs.image_uris import Framework, built_in_images, retrieve_image +from braket.jobs.local.local_job_container_setup import _get_env_input_data from braket.jobs.quantum_job import QuantumJob from braket.jobs.quantum_job_creation import _generate_default_job_name +DEFAULT_INPUT_CHANNEL = "input" +INNER_FUNCTION_SOURCE_INPUT_CHANNEL = "_braket_job_decorator_inner_function_source" +INNER_FUNCTION_SOURCE_INPUT_FOLDER = "_inner_function_source_folder" + def hybrid_job( *, @@ -74,6 +81,12 @@ def hybrid_job( `local` set to `True`: `wait_until_complete`, `instance_config`, `distribution`, `copy_checkpoints_from_job`, `stopping_condition`, `tags`, `logger`, and `quiet`. + Remarks: + Hybrid jobs created using this decorator have limited access to the source code of + functions defined outside of the decorated function. Functionality that depends on + source code analysis may not work properly when referencing functions defined outside + of the decorated function. + Args: device (str | None): Device ARN of the QPU device that receives priority quantum task queueing once the hybrid job begins running. Each QPU has a separate hybrid jobs @@ -178,9 +191,16 @@ def job_wrapper(*args: Any, **kwargs: Any) -> Callable: Returns: Callable: the callable for creating a Hybrid Job. """ - with _IncludeModules(include_modules), tempfile.TemporaryDirectory( - dir="", prefix="decorator_job_" - ) as temp_dir: + with ( + _IncludeModules(include_modules), + tempfile.TemporaryDirectory(dir="", prefix="decorator_job_") as temp_dir, + persist_inner_function_source(entry_point) as inner_source_input, + ): + + job_input_data = _add_inner_function_source_to_input_data( + input_data, inner_source_input + ) + temp_dir_path = Path(temp_dir) entry_point_file_path = Path("entry_point.py") with open(temp_dir_path / entry_point_file_path, "w") as entry_point_file: @@ -208,7 +228,7 @@ def job_wrapper(*args: Any, **kwargs: Any) -> Callable: } optional_args = { "image_uri": image_uri, - "input_data": input_data, + "input_data": job_input_data, "instance_config": instance_config, "distribution": distribution, "checkpoint_config": checkpoint_config, @@ -233,6 +253,111 @@ def job_wrapper(*args: Any, **kwargs: Any) -> Callable: return _hybrid_job +@contextmanager +def persist_inner_function_source(entry_point: callable) -> None: + """Persist the source code of the cloudpickled function by saving its source code as input data + and replace the source file path with the saved one. + Args: + entry_point (callable): The job decorated function. + """ + inner_source_mapping = _get_inner_function_source(entry_point.__code__) + + with tempfile.TemporaryDirectory() as temp_dir: + copy_dir = f"{temp_dir}/{INNER_FUNCTION_SOURCE_INPUT_FOLDER}" + os.mkdir(copy_dir) + path_mapping = _save_inner_source_to_file(inner_source_mapping, copy_dir) + entry_point.__code__ = _replace_inner_function_source_path( + entry_point.__code__, path_mapping + ) + yield {INNER_FUNCTION_SOURCE_INPUT_CHANNEL: copy_dir} + + +def _replace_inner_function_source_path( + code_object: CodeType, path_mapping: dict[str, str] +) -> CodeType: + """Recursively replace source code file path of the code object and of its child node's code + objects. + Args: + code_object (CodeType): Code object which source code file path to be replaced. + path_mapping (dict[str, str]): Mapping between local file path to path in a job + environment. + Returns: + CodeType: Code object with the source code file path replaced + """ + new_co_consts = [] + for const in code_object.co_consts: + if inspect.iscode(const): + new_path = path_mapping[const.co_filename] + const = const.replace(co_filename=new_path) + const = _replace_inner_function_source_path(const, path_mapping) + new_co_consts.append(const) + + code_object = code_object.replace(co_consts=tuple(new_co_consts)) + return code_object + + +def _save_inner_source_to_file(inner_source: dict[str, str], input_data_dir: str) -> dict[str, str]: + """Saves the source code as input data for a job and returns a dictionary that maps the local + source file path of a function to the one to be used in the job environment. + Args: + inner_source (dict[str, str]): Mapping between source file name and source code. + input_data_dir (str): The path of the folder to be uploaded to job as input data. + Returns: + dict[str, str]: Mapping between local file path to path in a job environment. + """ + path_mapping = {} + for i, (local_path, source_code) in enumerate(inner_source.items()): + copy_file_name = f"source_{i}.py" + with open(f"{input_data_dir}/{copy_file_name}", "w") as f: + f.write(source_code) + + path_mapping[local_path] = os.path.join( + _get_env_input_data()["AMZN_BRAKET_INPUT_DIR"], + INNER_FUNCTION_SOURCE_INPUT_CHANNEL, + copy_file_name, + ) + return path_mapping + + +def _get_inner_function_source(code_object: CodeType) -> dict[str, str]: + """Returns a dictionary that maps the source file name to source code for all source files + used by the inner functions inside the job decorated function. + Args: + code_object (CodeType): Code object of a inner function. + Returns: + dict[str, str]: Mapping between source file name and source code. + """ + inner_source = {} + for const in code_object.co_consts: + if inspect.iscode(const): + source_file_path = inspect.getfile(code_object) + lines, _ = inspect.findsource(code_object) + inner_source.update({source_file_path: "".join(lines)}) + inner_source.update(_get_inner_function_source(const)) + return inner_source + + +def _add_inner_function_source_to_input_data(input_data: dict, inner_source_input: dict) -> dict: + """Add the path of inner function source file as the input data of the job. + + Args: + input_data (dict): Provided input data of the job. + inner_source_input (dict): A dict that points to the path of inner function source file. + + Returns: + dict: input_data with inner function source file added. + """ + if input_data is None: + job_input_data = inner_source_input + elif isinstance(input_data, dict): + if INNER_FUNCTION_SOURCE_INPUT_CHANNEL in input_data: + raise ValueError(f"input channel cannot be {INNER_FUNCTION_SOURCE_INPUT_CHANNEL}") + job_input_data = {**input_data, **inner_source_input} + else: + job_input_data = {DEFAULT_INPUT_CHANNEL: input_data, **inner_source_input} + return job_input_data + + def _validate_python_version(image_uri: str | None, aws_session: AwsSession | None = None) -> None: """Validate python version at job definition time""" aws_session = aws_session or AwsSession() diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index b1739d879..c4d4a7a57 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -1,12 +1,13 @@ import ast import importlib +import inspect import re import sys import tempfile from logging import getLogger from pathlib import Path from ssl import PROTOCOL_TLS_CLIENT, SSLContext -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock, mock_open, patch import job_module import pytest @@ -35,13 +36,20 @@ def aws_session(): return aws_session +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_decorator_defaults( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -51,6 +59,7 @@ def my_entry(c=0, d: float = 1.0, **extras): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} source_module = mock_tempdir_name entry_point = f"{mock_tempdir_name}.entry_point:my_entry" @@ -69,11 +78,13 @@ def my_entry(c=0, d: float = 1.0, **extras): hyperparameters={"c": "0", "d": "1.0"}, logger=getLogger("braket.jobs.hybrid_job"), aws_session=aws_session, + input_data={}, ) assert mock_tempdir.return_value.__exit__.called @pytest.mark.parametrize("include_modules", (job_module, ["job_module"])) +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch("braket.jobs.image_uris.retrieve_image") @patch("sys.stdout") @patch("time.time", return_value=123.0) @@ -91,6 +102,7 @@ def test_decorator_non_defaults( mock_time, mock_stdout, mock_retrieve, + mock_persist_source, include_modules, ): mock_retrieve.return_value = "should-not-be-used" @@ -147,6 +159,7 @@ def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: mock_tempdir = MagicMock(spec=tempfile.TemporaryDirectory) mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = Devices.Amazon.SV1 source_module = mock_tempdir_name @@ -200,13 +213,20 @@ def my_entry(a, b: int, c=0, d: float = 1.0, **extras) -> str: mock_stdout.write.assert_any_call(s3_not_linked) +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_decorator_non_dict_input( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" input_prefix = "my_input" @@ -217,6 +237,7 @@ def my_entry(): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} source_module = mock_tempdir_name entry_point = f"{mock_tempdir_name}.entry_point:my_entry" @@ -234,19 +255,26 @@ def my_entry(): job_name="my-entry-123000", hyperparameters={}, logger=getLogger("braket.jobs.hybrid_job"), - input_data=input_prefix, + input_data={"input": input_prefix}, aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_decorator_list_dependencies( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" dependency_list = ["dep_1", "dep_2", "dep_3"] @@ -261,6 +289,7 @@ def my_entry(c=0, d: float = 1.0, **extras): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} source_module = mock_tempdir_name entry_point = f"{mock_tempdir_name}.entry_point:my_entry" @@ -278,6 +307,7 @@ def my_entry(c=0, d: float = 1.0, **extras): job_name="my-entry-123000", hyperparameters={"c": "0", "d": "1.0"}, logger=getLogger("braket.jobs.hybrid_job"), + input_data={}, aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called @@ -287,13 +317,20 @@ def my_entry(c=0, d: float = 1.0, **extras): ) +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(LocalQuantumJob, "create") def test_decorator_local( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -303,6 +340,7 @@ def my_entry(): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = Devices.Amazon.SV1 source_module = mock_tempdir_name @@ -316,18 +354,26 @@ def my_entry(): entry_point=entry_point, job_name="my-entry-123000", hyperparameters={}, + input_data={}, aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(LocalQuantumJob, "create") def test_decorator_local_unsupported_args( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -348,6 +394,7 @@ def my_entry(): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = Devices.Amazon.SV1 source_module = mock_tempdir_name @@ -361,18 +408,26 @@ def my_entry(): entry_point=entry_point, job_name="my-entry-123000", hyperparameters={}, + input_data={}, aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_job_name_too_long( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -382,6 +437,7 @@ def this_is_a_50_character_func_name_for_testing_names(): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = "local:braket/default" source_module = mock_tempdir_name @@ -403,19 +459,27 @@ def this_is_a_50_character_func_name_for_testing_names(): job_name=expected_job_name, hyperparameters={}, logger=getLogger("braket.jobs.hybrid_job"), + input_data={}, aws_session=aws_session, ) assert len(expected_job_name) == 50 assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_decorator_pos_only_slash( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -425,6 +489,7 @@ def my_entry(pos_only, /): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = "local:braket/default" source_module = mock_tempdir_name @@ -443,18 +508,26 @@ def my_entry(pos_only, /): job_name="my-entry-123000", hyperparameters={}, logger=getLogger("braket.jobs.hybrid_job"), + input_data={}, aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") @patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") @patch("time.time", return_value=123.0) @patch("builtins.open") @patch("tempfile.TemporaryDirectory") @patch.object(AwsQuantumJob, "create") def test_decorator_pos_only_args( - mock_create, mock_tempdir, _mock_open, mock_time, mock_retrieve, aws_session + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, ): mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" @@ -464,6 +537,7 @@ def my_entry(*args): mock_tempdir_name = "job_temp_dir_00000" mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} device = "local:braket/default" source_module = mock_tempdir_name @@ -482,11 +556,106 @@ def my_entry(*args): job_name="my-entry-123000", hyperparameters={}, logger=getLogger("braket.jobs.hybrid_job"), + input_data={}, + aws_session=aws_session, + ) + assert mock_tempdir.return_value.__exit__.called + + +@patch("builtins.open", new_callable=mock_open) +@patch.object(sys.modules["os"], "mkdir") +@patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") +@patch("time.time", return_value=123.0) +@patch("tempfile.TemporaryDirectory") +@patch.object(LocalQuantumJob, "create") +def test_decorator_persist_inner_function_source( + mock_create, mock_tempdir, mock_time, mock_retrieve, mock_mkdir, mock_file, aws_session +): + from braket.jobs.hybrid_job import ( + INNER_FUNCTION_SOURCE_INPUT_CHANNEL, + INNER_FUNCTION_SOURCE_INPUT_FOLDER, + ) + + mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" + + def my_entry(): + def inner_function_1(): + def inner_function_2(): + return "my inner function 2" + + return "my inner function 1" + + return inner_function_1 + + inner1 = my_entry() + + mock_tempdir_name = "job_temp_dir_00000" + mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + + device = Devices.Amazon.SV1 + source_module = mock_tempdir_name + entry_point = f"{mock_tempdir_name}.entry_point:my_entry" + + my_entry = hybrid_job(device=Devices.Amazon.SV1, local=True, aws_session=aws_session)(my_entry) + my_entry() + + expected_source = "".join(inspect.findsource(inner1)[0]) + assert mock_file().write.call_args_list[0][0][0] == expected_source + + expect_source_path = f"{mock_tempdir_name}/{INNER_FUNCTION_SOURCE_INPUT_FOLDER}/source_0.py" + assert mock_file.call_args_list[0][0][0] == expect_source_path + + mock_create.assert_called_with( + device=device, + source_module=source_module, + entry_point=entry_point, + job_name="my-entry-123000", + hyperparameters={}, aws_session=aws_session, + input_data={ + INNER_FUNCTION_SOURCE_INPUT_CHANNEL: f"{mock_tempdir_name}/" + f"{INNER_FUNCTION_SOURCE_INPUT_FOLDER}" + }, ) assert mock_tempdir.return_value.__exit__.called +@patch.object(sys.modules["braket.jobs.hybrid_job"], "persist_inner_function_source") +@patch.object(sys.modules["braket.jobs.hybrid_job"], "retrieve_image") +@patch("time.time", return_value=123.0) +@patch("builtins.open") +@patch("tempfile.TemporaryDirectory") +@patch.object(AwsQuantumJob, "create") +def test_decorator_conflict_channel_name( + mock_create, + mock_tempdir, + _mock_open, + mock_time, + mock_retrieve, + mock_persist_source, + aws_session, +): + from braket.jobs.hybrid_job import INNER_FUNCTION_SOURCE_INPUT_CHANNEL + + mock_retrieve.return_value = "00000000.dkr.ecr.us-west-2.amazonaws.com/latest" + + @hybrid_job( + device=None, + aws_session=aws_session, + input_data={INNER_FUNCTION_SOURCE_INPUT_CHANNEL: "foo-bar"}, + ) + def my_entry(c=0, d: float = 1.0, **extras): + return "my entry return value" + + mock_tempdir_name = "job_temp_dir_00000" + mock_tempdir.return_value.__enter__.return_value = mock_tempdir_name + mock_persist_source.return_value.__enter__.return_value = {} + + expect_error_message = f"input channel cannot be {INNER_FUNCTION_SOURCE_INPUT_CHANNEL}" + with pytest.raises(ValueError, match=expect_error_message): + my_entry() + + def test_serialization_error(aws_session): ssl_context = SSLContext(protocol=PROTOCOL_TLS_CLIENT) From e756ff85d5a241fcfcad1a07e1b5a53d674dec52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:02:35 -0500 Subject: [PATCH 249/347] infra: bump actions/checkout from 4.2.0 to 4.2.2 (#1050) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 348941fa1..f29692906 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -16,7 +16,7 @@ jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 76fb580a2..a8f21bd22 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -21,7 +21,7 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 557933a55..f22de208b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,7 +12,7 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a53a38227..fbea9297f 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -24,7 +24,7 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 0f6a34bed..2f48c46ab 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -14,7 +14,7 @@ jobs: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: From 6d0d49ae572e19a05e925c7f7f0ea60beacfd1fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:07:18 -0500 Subject: [PATCH 250/347] infra: bump codecov/codecov-action from 5.0.7 to 5.3.1 (#1057) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tim (Yi-Ting) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index fbea9297f..aa934cb48 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@015f24e6818733317a2da2edd6290ab26238649a # v5.0.7 + uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From eed884479a35462784ba68d484952d3fe701502c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:20:40 -0500 Subject: [PATCH 251/347] infra: bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.4 (#1058) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tim (Yi-Ting) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index f22de208b..9fa024c12 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # release/v1 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # release/v1 with: password: ${{ secrets.pypi_token }} From 808bb3996764b204e8cdcd13f382d7df6cf866db Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 7 Feb 2025 17:41:28 -0800 Subject: [PATCH 252/347] deprecation: Ankaa-2 (#1060) --- src/braket/devices/devices.py | 2 +- test/integ_tests/test_device_creation.py | 2 +- test/integ_tests/test_measure.py | 4 ++-- test/integ_tests/test_pulse.py | 12 ++++++------ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 44d6edb13..511bf3942 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -50,7 +50,7 @@ class _Rigetti(str, Enum): _AspenM1 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-1" _AspenM2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2" _AspenM3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" - Ankaa2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" + _Ankaa2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" class _Xanadu(str, Enum): _Borealis = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 07dce3eb8..c8994de34 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -51,7 +51,7 @@ def test_device_across_regions(aws_session, created_braket_devices): created_braket_devices[IQM_ARN] -@pytest.mark.parametrize("arn", [(RIGETTI_ARN), (IONQ_ARN), (IQM_ARN), (SIMULATOR_ARN)]) +@pytest.mark.parametrize("arn", [(IONQ_ARN), (IQM_ARN), (SIMULATOR_ARN)]) def test_get_devices_arn(arn): results = AwsDevice.get_devices(arns=[arn]) assert results[0].arn == arn diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py index c16713536..15e46bd3e 100644 --- a/test/integ_tests/test_measure.py +++ b/test/integ_tests/test_measure.py @@ -31,8 +31,8 @@ @pytest.mark.parametrize("arn", [(IONQ_ARN), (SIMULATOR_ARN)]) def test_unsupported_devices(arn): device = AwsDevice(arn) - if device.status == "OFFLINE": - pytest.skip("Device offline") + if device.status != "ONLINE": + pytest.skip("Device not online") circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) error_string = re.escape( diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index e5f42bcd3..363c8344b 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -202,8 +202,8 @@ def make_pulse( def test_pulse_bell(arbitrary_waveform, device): - if device.status == "OFFLINE": - pytest.skip("Device offline") + if device.status != "ONLINE": + pytest.skip("Device not online") ( a, b, @@ -252,8 +252,8 @@ def test_pulse_bell(arbitrary_waveform, device): def test_pulse_sequence(arbitrary_waveform, device): - if device.status == "OFFLINE": - pytest.skip("Device offline") + if device.status != "ONLINE": + pytest.skip("Device not online") ( a, b, @@ -307,8 +307,8 @@ def test_pulse_sequence(arbitrary_waveform, device): @pytest.mark.skip(reason="needs to be updated to work correctly on Ankaa-2") def test_gate_calibration_run(device, pulse_sequence): - if device.status == "OFFLINE": - pytest.skip("Device offline") + if device.status != "ONLINE": + pytest.skip("Device not online") user_gate_calibrations = GateCalibrations({(Gate.Rx(math.pi / 2), QubitSet(0)): pulse_sequence}) num_shots = 50 bell_circuit = Circuit().rx(0, math.pi / 2).rx(1, math.pi / 2).iswap(0, 1).rx(1, -math.pi / 2) From 92fa9050587b11ff67acd3532dd3e9b93b6d7243 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Feb 2025 16:15:06 +0000 Subject: [PATCH 253/347] prepare release v1.89.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37644d324..b3d7bfd9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.89.0 (2025-02-10) + +### Deprecations and Removals + + * Ankaa-2 + +### Features + + * support CUDA-Q decorator kernel with hybrid job decorator + ## v1.88.3 (2024-12-06) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5dee44e67..ae57df103 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.88.4.dev0" +__version__ = "1.89.0" From e069920bf8ef3b6a17f5b5c8b6483fc6e4cf805f Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Feb 2025 16:15:06 +0000 Subject: [PATCH 254/347] update development version to v1.89.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ae57df103..a44260dff 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.89.0" +__version__ = "1.89.1.dev0" From b533b89c18db611a013af4d6b545c7aae68367dc Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 10 Feb 2025 13:14:57 -0500 Subject: [PATCH 255/347] test: update target device for reservation integ test (#1062) --- test/integ_tests/test_reservation_arn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index 57833a840..36359c6fa 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -64,7 +64,7 @@ def test_create_job_with_decorator_via_invalid_reservation_arn(reservation_arn): with pytest.raises(ClientError, match="Reservation arn is invalid"): @hybrid_job( - device=Devices.IonQ.Aria1, + device=Devices.IQM.Garnet, reservation_arn=reservation_arn, ) def hello_job(): From d3aa29bb7824e4f6e47ae91e9002615bd48a7f63 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Mon, 10 Feb 2025 13:27:09 -0500 Subject: [PATCH 256/347] fix: decorator job with no inner function (#1061) --- src/braket/jobs/hybrid_job.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index f3e1661a1..0f80e7fd9 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -262,14 +262,17 @@ def persist_inner_function_source(entry_point: callable) -> None: """ inner_source_mapping = _get_inner_function_source(entry_point.__code__) - with tempfile.TemporaryDirectory() as temp_dir: - copy_dir = f"{temp_dir}/{INNER_FUNCTION_SOURCE_INPUT_FOLDER}" - os.mkdir(copy_dir) - path_mapping = _save_inner_source_to_file(inner_source_mapping, copy_dir) - entry_point.__code__ = _replace_inner_function_source_path( - entry_point.__code__, path_mapping - ) - yield {INNER_FUNCTION_SOURCE_INPUT_CHANNEL: copy_dir} + if len(inner_source_mapping) == 0: + yield {} + else: + with tempfile.TemporaryDirectory(dir="", prefix="decorator_job_inner_source_") as temp_dir: + copy_dir = f"{temp_dir}/{INNER_FUNCTION_SOURCE_INPUT_FOLDER}" + os.mkdir(copy_dir) + path_mapping = _save_inner_source_to_file(inner_source_mapping, copy_dir) + entry_point.__code__ = _replace_inner_function_source_path( + entry_point.__code__, path_mapping + ) + yield {INNER_FUNCTION_SOURCE_INPUT_CHANNEL: copy_dir} def _replace_inner_function_source_path( From 0a30eb0e6edf408fdaf03afccbdbc7a1b5f2e087 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Feb 2025 21:59:06 +0000 Subject: [PATCH 257/347] prepare release v1.89.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d7bfd9a..654eba5cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.89.1 (2025-02-10) + +### Bug Fixes and Other Changes + + * decorator job with no inner function + ## v1.89.0 (2025-02-10) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a44260dff..77fec49e3 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.89.1.dev0" +__version__ = "1.89.1" From a5f3fe700246543fd4d1e71298f823fceff8f486 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Feb 2025 21:59:06 +0000 Subject: [PATCH 258/347] update development version to v1.89.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 77fec49e3..4f06b7202 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.89.1" +__version__ = "1.89.2.dev0" From 8753ca645d82445450bf3f139c6d87b7065f45cf Mon Sep 17 00:00:00 2001 From: Jacob Feldman Date: Fri, 14 Feb 2025 13:07:43 -0800 Subject: [PATCH 259/347] feature: added ankaa-3 to enum (#1065) --- src/braket/devices/devices.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 511bf3942..d66ca6654 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -51,6 +51,7 @@ class _Rigetti(str, Enum): _AspenM2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-2" _AspenM3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Aspen-M-3" _Ankaa2 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-2" + Ankaa3 = "arn:aws:braket:us-west-1::device/qpu/rigetti/Ankaa-3" class _Xanadu(str, Enum): _Borealis = "arn:aws:braket:us-east-1::device/qpu/xanadu/Borealis" From 493dcad6a9a2bd191dc739a8fc0bce372c3ceac6 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 14 Feb 2025 21:22:41 +0000 Subject: [PATCH 260/347] prepare release v1.90.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 654eba5cc..f98823f24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.90.0 (2025-02-14) + +### Features + + * added ankaa-3 to enum + ## v1.89.1 (2025-02-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4f06b7202..21d137722 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.89.2.dev0" +__version__ = "1.90.0" From ed6b84931b6613f728c167fc29a28a71b2309cad Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 14 Feb 2025 21:22:41 +0000 Subject: [PATCH 261/347] update development version to v1.90.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 21d137722..d3454fb9f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.0" +__version__ = "1.90.1.dev0" From c327e54498f2c1bc05a6eab91553b4b49fef7977 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:11:00 -0500 Subject: [PATCH 262/347] infra: bump codecov/codecov-action from 5.3.1 to 5.4.0 (#1067) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index aa934cb48..282f29c06 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From a8528a5d154ead2d1b41358099e04c025aadb976 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:26:16 -0500 Subject: [PATCH 263/347] infra: bump actions/setup-python from 5.3.0 to 5.4.0 (#1066) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index f29692906..a65d87658 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index a8f21bd22..69181b1fe 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 9fa024c12..47cf1d1b1 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 282f29c06..63e63a488 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 2f48c46ab..9a3e3431f 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 + uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 with: python-version: '3.x' - name: Install wheel From 3282dcc00087d69bc5edb5cda75db387ec31b3c6 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 4 Mar 2025 16:40:04 -0500 Subject: [PATCH 264/347] fix: Update AwsQuantumTask.__init__ signature (#1068) --- src/braket/aws/aws_quantum_task.py | 7 +++++++ test/unit_tests/braket/aws/test_aws_quantum_task.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index a21c7782c..4dc4940fb 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -15,6 +15,7 @@ import asyncio import time +import warnings from functools import singledispatch from logging import Logger, getLogger from typing import Any, ClassVar, Optional, Union @@ -233,6 +234,7 @@ def __init__( poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), quiet: bool = False, + **kwargs, ): """Initializes an `AwsQuantumTask`. @@ -260,6 +262,11 @@ def __init__( >>> result = task.result() GateModelQuantumTaskResult(...) """ + if kwargs: + warnings.warn( + f"AwsQuantumTask.__init__ received unknown keyword args: {list(kwargs.keys())}" + ) + self._arn: str = arn self._aws_session: AwsSession = aws_session or AwsQuantumTask._aws_session_for_task_arn( task_arn=arn diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 51fe2c75f..cfe306127 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -15,6 +15,7 @@ import json import threading import time +import warnings from unittest.mock import MagicMock, Mock, patch import pytest @@ -204,6 +205,17 @@ def test_no_id_setter(quantum_task): quantum_task.id = 123 +def test_no_unknown_kwargs_no_warnings(arn, aws_session): + with warnings.catch_warnings(): + warnings.simplefilter("error") + AwsQuantumTask(arn, aws_session) + + +def test_unknown_kwarg_warning(arn, aws_session): + with pytest.warns(UserWarning): + AwsQuantumTask(arn, aws_session, unknown_kwarg=123) + + def test_metadata(quantum_task): metadata_1 = {"status": "RUNNING"} quantum_task._aws_session.get_quantum_task.return_value = metadata_1 From 7b63afa182e1dc1c5cf9f0105c1d97f674f6777d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:06:27 -0500 Subject: [PATCH 265/347] fix: Set user agent in Boto3 config object (#1069) --- src/braket/aws/aws_session.py | 36 +++++----- .../unit_tests/braket/aws/test_aws_session.py | 71 +++++++++---------- 2 files changed, 53 insertions(+), 54 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index b4cdfcd31..83a690c27 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -72,13 +72,22 @@ def __init__( f"Braket Client region is '{braket_client.meta.region_name}'." ) - self._config = config + self._update_user_agent() + self._config = Config(user_agent_extra=self._braket_user_agents) + if config: + self._config = self._config.merge(config) if braket_client: + braket_client._client_config = ( + self._config.merge(braket_client._client_config) + if braket_client._client_config + else self._config + ) self.boto_session = boto_session or boto3.Session( region_name=braket_client.meta.region_name ) self.braket_client = braket_client + self._config = braket_client._client_config else: self.boto_session = boto_session or boto3.Session( region_name=os.environ.get("AWS_REGION") @@ -86,15 +95,12 @@ def __init__( self.braket_client = self.boto_session.client( "braket", config=self._config, endpoint_url=os.environ.get("BRAKET_ENDPOINT") ) - self._update_user_agent() + self._braket_user_agents = self._config._user_provided_options["user_agent_extra"] self._custom_default_bucket = bool(default_bucket) self._default_bucket = default_bucket or os.environ.get("AMZN_BRAKET_OUT_S3_BUCKET") self.braket_client.meta.events.register( "before-sign.braket.CreateQuantumTask", self._add_cost_tracker_count_handler ) - self.braket_client.meta.events.register( - "before-sign.braket", self._add_braket_user_agents_handler - ) self._iam = None self._s3 = None @@ -201,14 +207,11 @@ def add_braket_user_agent(self, user_agent: str) -> None: if user_agent not in self._braket_user_agents: self._braket_user_agents = f"{self._braket_user_agents} {user_agent}" - def _add_braket_user_agents_handler(self, request: awsrequest.AWSRequest, **kwargs) -> None: - try: - initial_user_agent = request.headers["User-Agent"] - request.headers.replace_header( - "User-Agent", f"{initial_user_agent} {self._braket_user_agents}" - ) - except KeyError: - request.headers.add_header("User-Agent", self._braket_user_agents) + new_user_agent_config = Config(user_agent_extra=self._braket_user_agents) + updated_config = self.braket_client._client_config.merge(new_user_agent_config) + self.braket_client = self.boto_session.client( + "braket", config=updated_config, endpoint_url=os.environ.get("BRAKET_ENDPOINT") + ) @staticmethod def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> None: @@ -822,7 +825,10 @@ def copy_session( Returns: AwsSession: based on the region and boto config parameters. """ - config = Config(max_pool_connections=max_connections) if max_connections else None + config = Config(user_agent_extra=self._braket_user_agents) + if max_connections: + config = config.merge(Config(max_pool_connections=max_connections)) + session_region = self.boto_session.region_name new_region = region or session_region @@ -852,8 +858,6 @@ def copy_session( copied_session = AwsSession( boto_session=boto_session, config=config, default_bucket=default_bucket ) - # Preserve user_agent information - copied_session._braket_user_agents = self._braket_user_agents return copied_session @cache diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index fb0b309fb..a7cb4aa0d 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -43,10 +43,19 @@ def boto_session(): return _boto_session +@pytest.fixture +def boto_config(): + _boto_config = Mock() + _boto_config._user_provided_options = {} + return _boto_config + + @pytest.fixture def braket_client(): _braket_client = Mock() _braket_client.meta.region_name = "us-west-2" + _braket_client._client_config = Mock() + _braket_client._client_config._user_provided_options = {} return _braket_client @@ -167,31 +176,31 @@ def throttling_response(): def test_initializes_boto_client_if_required(boto_session): - AwsSession(boto_session=boto_session) - boto_session.client.assert_any_call("braket", config=None, endpoint_url=None) + aws_session = AwsSession(boto_session=boto_session) + boto_session.client.assert_any_call("braket", config=aws_session._config, endpoint_url=None) -def test_user_supplied_braket_client(): +def test_user_supplied_braket_client(boto_config): boto_session = Mock() boto_session.region_name = "foobar" braket_client = Mock() braket_client.meta.region_name = "foobar" + braket_client._client_config = boto_config aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) assert aws_session.braket_client == braket_client @patch.dict("os.environ", {"BRAKET_ENDPOINT": "some-endpoint"}) -def test_config(boto_session): - config = Mock() - AwsSession(boto_session=boto_session, config=config) +def test_config(boto_session, boto_config): + aws_session = AwsSession(boto_session=boto_session, config=boto_config) boto_session.client.assert_any_call( "braket", - config=config, + config=aws_session._config, endpoint_url="some-endpoint", ) -def test_region(): +def test_region(boto_config): boto_region = "boto-region" braket_region = "braket-region" @@ -199,6 +208,7 @@ def test_region(): boto_session.region_name = boto_region braket_client = Mock() braket_client.meta.region_name = braket_region + braket_client._client_config = boto_config assert ( AwsSession( @@ -270,40 +280,27 @@ def test_ecr(aws_session): @patch("os.path.exists") -@pytest.mark.parametrize( - "metadata_file_exists, initial_user_agent", - [ - (True, None), - (False, None), - (True, ""), - (False, ""), - (True, "Boto3/1.17.18 Python/3.7.10"), - (False, "Boto3/1.17.18 Python/3.7.10 exec-env/AWS_Lambda_python3.7"), - ], -) -def test_populates_user_agent(os_path_exists_mock, metadata_file_exists, initial_user_agent): +@pytest.mark.parametrize("metadata_file_exists", [True, False]) +def test_populates_user_agent(os_path_exists_mock, metadata_file_exists, boto_config): request = Mock() request.headers = HTTPMessage() boto_session = Mock() boto_session.region_name = "foobar" braket_client = Mock() braket_client.meta.region_name = "foobar" + braket_client._client_config = boto_config + nbi_metadata_path = "/opt/ml/metadata/resource-metadata.json" os_path_exists_mock.return_value = metadata_file_exists aws_session = AwsSession(boto_session=boto_session, braket_client=braket_client) + os_path_exists_mock.assert_called_with(nbi_metadata_path) + expected_user_agent = ( f"BraketSdk/{braket_sdk.__version__} " f"BraketSchemas/{braket_schemas.__version__} " f"NotebookInstance/{0 if metadata_file_exists else None}" ) - os_path_exists_mock.assert_called_with(nbi_metadata_path) - - if initial_user_agent is not None: - request.headers.add_header("User-Agent", initial_user_agent) - expected_user_agent = f"{initial_user_agent} {expected_user_agent}" - - aws_session._add_braket_user_agents_handler(request) - assert request.headers.get("User-Agent") == expected_user_agent + assert aws_session._braket_user_agents == expected_user_agent @patch("braket.aws.aws_session.active_trackers") @@ -317,7 +314,7 @@ def test_add_cost_tracker_count(active_trackers_mock, aws_session): request.headers.add_header.assert_called_with("Braket-Trackers", "0") -def test_retrieve_s3_object_body_success(boto_session): +def test_retrieve_s3_object_body_success(boto_session, boto_config): bucket_name = "braket-integ-test" filename = "tasks/test_task_1.json" @@ -332,16 +329,14 @@ def test_retrieve_s3_object_body_success(boto_session): mock_read_object.decode.return_value = json.dumps(TEST_S3_OBJ_CONTENTS) json.dumps(TEST_S3_OBJ_CONTENTS) - aws_session = AwsSession(boto_session=boto_session) + aws_session = AwsSession(boto_session=boto_session, config=None) return_value = aws_session.retrieve_s3_object_body(bucket_name, filename) assert return_value == json.dumps(TEST_S3_OBJ_CONTENTS) - boto_session.resource.assert_called_with("s3", config=None) + boto_session.resource.assert_called_with("s3", config=aws_session._config) - config = Mock() - AwsSession(boto_session=boto_session, config=config).retrieve_s3_object_body( - bucket_name, filename - ) - boto_session.resource.assert_called_with("s3", config=config) + aws_session = AwsSession(boto_session=boto_session, config=boto_config) + aws_session.retrieve_s3_object_body(bucket_name, filename) + boto_session.resource.assert_called_with("s3", config=aws_session._config) @pytest.mark.xfail(raises=ClientError) @@ -1324,13 +1319,13 @@ def test_get_log_events(aws_session, next_token): @patch("boto3.Session") def test_copy_session(boto_session_init, aws_session): boto_session_init.return_value = Mock() - aws_session._braket_user_agents = "foo/bar" + aws_session.add_braket_user_agent("foo/bar") copied_session = AwsSession.copy_session(aws_session, "us-west-2") boto_session_init.assert_called_with( region_name="us-west-2", profile_name="test-profile", ) - assert copied_session._braket_user_agents == "foo/bar" + assert "foo/bar" in copied_session._braket_user_agents assert copied_session._default_bucket is None From 5aee5e36948a53e257019207bdc9e33bf7341a9e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 6 Mar 2025 22:20:46 +0000 Subject: [PATCH 266/347] prepare release v1.90.1 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f98823f24..f6fbe2f39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.90.1 (2025-03-06) + +### Bug Fixes and Other Changes + + * Set user agent in Boto3 config object + * Update AwsQuantumTask.__init__ signature + ## v1.90.0 (2025-02-14) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d3454fb9f..35ff2f1bb 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.1.dev0" +__version__ = "1.90.1" From 493f4d65c59264d1f03496328ae4494b992dc646 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 6 Mar 2025 22:20:46 +0000 Subject: [PATCH 267/347] update development version to v1.90.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 35ff2f1bb..18f47ccd8 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.1" +__version__ = "1.90.2.dev0" From 7a4271d8aae698ce25fb61f1610f1c54aaac6fe7 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:58:01 -0500 Subject: [PATCH 268/347] fix: onboard to use ruff (#1029) --- .coveragerc | 2 - program.py | 4 + pyproject.toml | 86 +- setup.cfg | 25 - .../ahs/analog_hamiltonian_simulation.py | 6 +- src/braket/ahs/atom_arrangement.py | 6 +- src/braket/ahs/driving_field.py | 12 +- src/braket/ahs/field.py | 7 +- src/braket/ahs/local_detuning.py | 4 +- src/braket/annealing/problem.py | 3 +- src/braket/aws/aws_device.py | 92 +- src/braket/aws/aws_quantum_job.py | 25 +- src/braket/aws/aws_quantum_task.py | 120 +- src/braket/aws/aws_quantum_task_batch.py | 111 +- src/braket/aws/aws_session.py | 36 +- src/braket/aws/direct_reservations.py | 7 +- src/braket/circuits/__init__.py | 37 +- src/braket/circuits/angled_gate.py | 42 +- src/braket/circuits/ascii_circuit_diagram.py | 4 +- src/braket/circuits/circuit.py | 149 +- src/braket/circuits/circuit_helpers.py | 6 +- src/braket/circuits/compiler_directive.py | 5 +- src/braket/circuits/gate.py | 23 +- src/braket/circuits/gate_calibrations.py | 48 +- src/braket/circuits/gates.py | 351 ++-- src/braket/circuits/instruction.py | 22 +- src/braket/circuits/measure.py | 5 +- src/braket/circuits/moments.py | 7 +- src/braket/circuits/noise.py | 69 +- src/braket/circuits/noise_helpers.py | 23 +- src/braket/circuits/noise_model/__init__.py | 21 +- src/braket/circuits/noise_model/criteria.py | 4 +- .../circuits/noise_model/noise_model.py | 13 +- src/braket/circuits/noises.py | 6 +- src/braket/circuits/observable.py | 20 +- src/braket/circuits/observables.py | 49 +- src/braket/circuits/quantum_operator.py | 6 +- .../circuits/quantum_operator_helpers.py | 7 +- src/braket/circuits/result_type.py | 13 +- src/braket/circuits/result_types.py | 26 +- .../ascii_circuit_diagram.py | 16 +- .../text_circuit_diagram.py | 4 +- .../text_circuit_diagram_utils.py | 6 +- .../unicode_circuit_diagram.py | 24 +- src/braket/circuits/translations.py | 19 +- src/braket/devices/device.py | 8 +- src/braket/devices/local_simulator.py | 79 +- src/braket/jobs/data_persistence.py | 16 +- src/braket/jobs/environment_variables.py | 2 +- src/braket/jobs/hybrid_job.py | 61 +- src/braket/jobs/image_uris.py | 2 +- src/braket/jobs/local/local_job.py | 11 +- src/braket/jobs/local/local_job_container.py | 88 +- .../jobs/local/local_job_container_setup.py | 2 +- src/braket/jobs/logs.py | 21 +- src/braket/jobs/metrics.py | 14 +- .../cwl_insights_metrics_fetcher.py | 17 +- .../jobs/metrics_data/cwl_metrics_fetcher.py | 4 +- .../jobs/metrics_data/log_metrics_parser.py | 35 +- src/braket/jobs/quantum_job_creation.py | 6 +- src/braket/jobs/serialization.py | 2 +- src/braket/parametric/free_parameter.py | 3 +- .../parametric/free_parameter_expression.py | 65 +- src/braket/parametric/parameterizable.py | 6 +- src/braket/pulse/__init__.py | 12 +- src/braket/pulse/ast/approximation_parser.py | 73 +- src/braket/pulse/ast/free_parameters.py | 2 +- src/braket/pulse/frame.py | 4 +- src/braket/pulse/port.py | 4 +- src/braket/pulse/pulse_sequence.py | 37 +- src/braket/pulse/waveforms.py | 83 +- .../quantum_information/pauli_string.py | 36 +- src/braket/registers/qubit_set.py | 6 +- src/braket/tasks/__init__.py | 4 +- ...iltonian_simulation_quantum_task_result.py | 4 +- .../tasks/annealing_quantum_task_result.py | 3 +- .../tasks/gate_model_quantum_task_result.py | 51 +- src/braket/tasks/local_quantum_task.py | 12 +- src/braket/tasks/quantum_task.py | 4 +- src/braket/timings/time_series.py | 7 +- src/braket/tracking/pricing.py | 6 +- src/braket/tracking/tracker.py | 14 +- src/braket/tracking/tracking_context.py | 6 +- src/braket/tracking/tracking_events.py | 3 +- .../gate_model_device_testing_utils.py | 56 +- test/integ_tests/test_pulse.py | 254 ++- .../ahs/test_analog_hamiltonian_simulation.py | 24 +- .../braket/aws/common_test_utils.py | 284 ++- test/unit_tests/braket/aws/test_aws_device.py | 119 +- .../braket/aws/test_aws_quantum_job.py | 86 +- .../braket/aws/test_aws_quantum_task.py | 118 +- .../braket/aws/test_aws_quantum_task_batch.py | 12 +- .../braket/circuits/noise/test_noise_model.py | 14 +- .../braket/circuits/test_basis_state.py | 12 +- .../braket/circuits/test_circuit.py | 1638 ++++++++--------- .../braket/circuits/test_gate_calibration.py | 88 +- test/unit_tests/braket/circuits/test_gates.py | 14 +- .../braket/circuits/test_measure.py | 10 +- .../unit_tests/braket/circuits/test_noises.py | 40 +- .../braket/circuits/test_observable.py | 6 +- .../braket/circuits/test_observables.py | 42 +- .../braket/devices/test_local_simulator.py | 411 ++--- .../braket/jobs/local/test_local_job.py | 44 +- .../jobs/local/test_local_job_container.py | 173 +- .../local/test_local_job_container_setup.py | 24 +- .../braket/jobs/test_data_persistence.py | 150 +- .../braket/jobs/test_environment_variables.py | 5 +- .../unit_tests/braket/jobs/test_hybrid_job.py | 4 +- .../braket/pulse/test_pulse_sequence.py | 275 ++- .../quantum_information/test_pauli_string.py | 2 +- .../test_gate_model_quantum_task_result.py | 121 +- .../tasks/test_local_quantum_task_batch.py | 8 +- tox.ini | 38 +- 113 files changed, 3106 insertions(+), 3350 deletions(-) create mode 100644 program.py diff --git a/.coveragerc b/.coveragerc index 22cb4caeb..98f09ec04 100644 --- a/.coveragerc +++ b/.coveragerc @@ -35,8 +35,6 @@ exclude_lines = # Avoid type checking import conditionals if TYPE_CHECKING: - - [html] directory = build/coverage diff --git a/program.py b/program.py new file mode 100644 index 000000000..45690eccf --- /dev/null +++ b/program.py @@ -0,0 +1,4 @@ +from braket.aws import AwsDevice + +device = AwsDevice("arn:aws:braket::us-west-1:device/qpu/rigetti/Ankaa-2") +print(device) diff --git a/pyproject.toml b/pyproject.toml index aa4949aa1..7d37cb26b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,86 @@ -[tool.black] +[tool.ruff] +target-version = "py39" line-length = 100 +format.preview = true +format.docstring-code-line-length = 100 +format.docstring-code-format = true +lint.select = [ + "ALL", +] +lint.ignore = [ + "A001", "A002", # This will be refactored out later + "ANN202", "ARG001", "ARG002", # TODO after onboarding + "ANN002", # Missing tpye annotation for `*args` in method + "ANN003", # Missing tpye annotation for `**kwargs` in method + "ANN204", # Special functions don't need return types + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `arg`" + "B008", # Logger deinition rework + "B026", # Accepted pattern to unpack after keyword args + "BLE001", # This needs to be cleaned up later. + "COM812", # conflicts with formatter + "CPY", # No copyright header + "D", # ignore documentation for now + "D203", # `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible + "D212", # `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible + "DOC201", # no restructuredtext support yet + "DOC502", # Exceptions are documented in top level functions and no in the private methods + "DOC501", # broken with sphinx docs + "EM", # error string messages are fine + "ERA001", # This is used to capture math + "FA100", # Ignore until Python 3.10 is used + "FBT", # Booleans in methods are fine + "FIX", # Allow TODO statements for now. + "FURB101", # Same as PTH + "FURB103", # Same as PTH + "G004", # Let f strings in logger + "INP001", # no implicit namespaces here + "ISC001", # conflicts with formatter + "N802", # Function names are set now + "N803", # Allow uppercase + "N806", # Vars will break this rule based on technical names. + "N815", # Allowing reviwers to do a per case basis + "PLC2701", # Allow private function access in code + "PLR0913", # Too many variables in function + "PLR0914", # Too many local variables + "PLR0917", # Too many positional arguments + "PLR6301", # Ignore for dispath functions + "PLR2004", # Allow magic values + "PLW1510", # Subprocess implicitly uses this + "PLW0603", # Allow usage of global vars + "PLW1641", # Object with equal and not __hash__ + "PTH", # Prefer builtin path + "PYI034", # Return class type hint here instead of self + "S104", # Possible binding to all interfaces + "S403", # Pickle is used + "S404", # Using subprocess is alright. + "S603", # Using subprocess is alright. + "S607", # Partial path + "SLF", # Private methods are allowed for access + "T201", # Print statements are used in some places. Later this should be moved inline. + "TCH", # This is good to look into later on. TODO + "TD", # Allow TODO Statements + "TRY003", # Longer messages are fine. +] +lint.per-file-ignores."tests/**/*.py" = [ + "D", # don't care about documentation in tests + "FBT", # don"t care about booleans as positional arguments in tests + "INP001", # no implicit namespace + "PLR2004", # Magic value used in comparison, consider replacing with a constant variable + "S101", # asserts allowed in tests... + "S603", # `subprocess` call: check for execution of untrusted input +] +lint.isort = { known-first-party = [ + "amazon-braket-sdk", + "tests", +] } +lint.preview = true +lint.pyupgrade.keep-runtime-typing = true + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.flake8-annotations] +mypy-init-return = false + +[tool.ruff.lint.flake8-builtins] +builtins-allowed-modules = ["operator"] diff --git a/setup.cfg b/setup.cfg index ab93f5955..0abcc1275 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,29 +13,4 @@ filterwarnings= # Ref: https://github.com/pytest-dev/pytest-cov/issues/557 ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning -[isort] -line_length = 100 -multi_line_output = 3 -include_trailing_comma = true -profile = black -[flake8] -ignore = - # not pep8, black adds whitespace before ':' - E203, - # not pep8, https://www.python.org/dev/peps/pep-0008/#pet-peeves - E231, - # not pep8, black adds line break before binary operator - W503, - # Google Python style is not RST until after processed by Napoleon - # See https://github.com/peterjc/flake8-rst-docstrings/issues/17 - RST201,RST203,RST301, -max_line_length = 100 -max-complexity = 10 -exclude = - __pycache__ - .tox - .git - bin - build - venv diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index f11675d41..d22e4431b 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -15,6 +15,7 @@ from collections import defaultdict from functools import singledispatch +from typing import TYPE_CHECKING import braket.ir.ahs as ir from braket.ahs.atom_arrangement import AtomArrangement, SiteType @@ -25,6 +26,9 @@ from braket.device_schema import DeviceActionType from braket.timings.time_series import TimeSeries +if TYPE_CHECKING: + from braket.aws import AwsDevice + class AnalogHamiltonianSimulation: LOCAL_DETUNING_PROPERTY = "local_detuning" @@ -124,7 +128,7 @@ def _hamiltonian_to_ir(self) -> ir.Hamiltonian: localDetuning=terms[AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY], ) - def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: # noqa + def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: """Creates a new AnalogHamiltonianSimulation with all numerical values represented as Decimal objects with fixed precision based on the capabilities of the device. diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py index 24d4fc9aa..fc52d3f05 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -18,7 +18,6 @@ from decimal import Decimal from enum import Enum from numbers import Number -from typing import Union import numpy as np @@ -63,7 +62,7 @@ def __init__(self): def add( self, - coordinate: Union[tuple[Number, Number], np.ndarray], + coordinate: tuple[Number, Number] | np.ndarray, site_type: SiteType = SiteType.FILLED, ) -> AtomArrangement: """Add a coordinate to the atom arrangement. @@ -124,6 +123,7 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: round(Decimal(c) / position_res) * position_res for c in site.coordinate ) discretized_arrangement.add(new_coordinates, site.site_type) - return discretized_arrangement except Exception as e: raise DiscretizationError(f"Failed to discretize register {e}") from e + else: + return discretized_arrangement diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index cbf01838d..11d30e34a 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -13,8 +13,6 @@ from __future__ import annotations -from typing import Union - from braket.ahs.discretization_types import DiscretizationProperties from braket.ahs.field import Field from braket.ahs.hamiltonian import Hamiltonian @@ -24,9 +22,9 @@ class DrivingField(Hamiltonian): def __init__( self, - amplitude: Union[Field, TimeSeries], - phase: Union[Field, TimeSeries], - detuning: Union[Field, TimeSeries], + amplitude: Field | TimeSeries, + phase: Field | TimeSeries, + detuning: Field | TimeSeries, ) -> None: r"""Creates a Hamiltonian term :math:`H_{drive}` for the driving field that coherently transfers atoms from the ground state to the Rydberg state @@ -181,6 +179,4 @@ def from_lists( detuning.put(t, detuning_value) phase.put(t, phase_value) - drive = DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) - - return drive + return DrivingField(amplitude=amplitude, detuning=detuning, phase=phase) diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index 28319cc99..f9c1af16c 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -64,8 +64,7 @@ def discretize( discretized_pattern = None else: discretized_pattern = self.pattern.discretize(pattern_resolution) - discretized_field = Field(time_series=discretized_time_series, pattern=discretized_pattern) - return discretized_field + return Field(time_series=discretized_time_series, pattern=discretized_pattern) @staticmethod def from_lists(times: list[Decimal], values: list[Decimal], pattern: list[Decimal]) -> Field: @@ -90,6 +89,4 @@ def from_lists(times: list[Decimal], values: list[Decimal], pattern: list[Decima time_series = TimeSeries.from_lists(times=times, values=values) - field = Field(time_series=time_series, pattern=Pattern(pattern)) - - return field + return Field(time_series=time_series, pattern=Pattern(pattern)) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 00b420210..43b7abaad 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -81,9 +81,7 @@ def from_lists(times: list[float], values: list[float], pattern: list[float]) -> magnitude = TimeSeries() for t, v in zip(times, values): magnitude.put(t, v) - shift = LocalDetuning(Field(magnitude, Pattern(pattern))) - - return shift + return LocalDetuning(Field(magnitude, Pattern(pattern))) def stitch( self, other: LocalDetuning, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index 9515cbfa6..80bea2d37 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -148,7 +148,6 @@ def to_ir(self) -> Problem: type=ir.ProblemType[self._problem_type.value], linear=self._linear, quadratic={ - ",".join((str(q1), str(q2))): self._quadratic[(q1, q2)] - for q1, q2 in self._quadratic + ",".join((str(q1), str(q2))): self._quadratic[q1, q2] for q1, q2 in self._quadratic }, ) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 598271424..e2cec5106 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -18,9 +18,9 @@ import os import urllib.request import warnings -from datetime import datetime +from datetime import datetime, timezone from enum import Enum -from typing import Any, ClassVar, Optional, Union +from typing import Any, ClassVar, Optional from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -70,7 +70,7 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) - _RIGETTI_GATES_TO_BRAKET: ClassVar[dict[str, str | None]] = { + _RIGETTI_GATES_TO_BRAKET: ClassVar[Optional[dict[str, str]]] = { # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. "Rx_12": None, "Cz": "CZ", @@ -120,21 +120,19 @@ def __init__( def run( self, - task_specification: Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], + task_specification: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | PulseSequence + | AnalogHamiltonianSimulation, s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: Optional[float] = None, inputs: Optional[dict[str, float]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, - reservation_arn: str | None = None, + reservation_arn: Optional[str] = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, ) -> AwsQuantumTask: @@ -202,7 +200,7 @@ def run( See Also: `braket.aws.aws_quantum_task.AwsQuantumTask.create()` - """ # noqa E501 + """ # noqa: E501 if self._noise_model: task_specification = self._apply_noise_model_to_circuit(task_specification) return AwsQuantumTask.create( @@ -228,25 +226,19 @@ def run( def run_batch( self, - task_specifications: Union[ - Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], - list[ - Union[ - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ] - ], + task_specifications: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | PulseSequence + | AnalogHamiltonianSimulation + | list[ + Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | PulseSequence + | AnalogHamiltonianSimulation ], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, @@ -254,7 +246,7 @@ def run_batch( max_connections: int = AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Optional[Union[dict[str, float], list[dict[str, float]]]] = None, + inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, reservation_arn: Optional[str] = None, *aws_quantum_task_args, @@ -299,7 +291,7 @@ def run_batch( See Also: `braket.aws.aws_quantum_task_batch.AwsQuantumTaskBatch` - """ # noqa E501 + """ # noqa: E501 if self._noise_model: task_specifications = [ self._apply_noise_model_to_circuit(task_specification) @@ -349,33 +341,36 @@ def _get_regional_device_session(self, session: AwsSession) -> AwsSession: ) try: self._populate_properties(region_session) - return region_session except ClientError as e: raise ( ValueError(f"'{self._arn}' not found") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e ) from e + else: + return region_session def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: current_region = session.region try: self._populate_properties(session) - return session except ClientError as e: if e.response["Error"]["Code"] != "ResourceNotFoundException": - raise e + raise if "qpu" not in self._arn: raise ValueError(f"Simulator '{self._arn}' not found in '{current_region}'") from e + else: + return session # Search remaining regions for QPU for region in frozenset(AwsDevice.REGIONS) - {current_region}: region_session = AwsSession.copy_session(session, region) try: self._populate_properties(region_session) - return region_session except ClientError as e: if e.response["Error"]["Code"] != "ResourceNotFoundException": - raise e + raise + else: + return region_session raise ValueError(f"QPU '{self._arn}' not found") def _populate_properties(self, session: AwsSession) -> None: @@ -439,7 +434,7 @@ def is_available(self) -> bool: is_available_result = False - current_datetime_utc = datetime.utcnow() + current_datetime_utc = datetime.now(tz=timezone.utc) for execution_window in self.properties.service.executionWindows: weekday = current_datetime_utc.weekday() current_time_utc = current_datetime_utc.time().replace(microsecond=0) @@ -537,13 +532,12 @@ def _construct_topology_graph(self) -> DiGraph: i = item[0] edges.extend([(int(i), int(j)) for j in item[1]]) return from_edgelist(edges, create_using=DiGraph()) - elif hasattr(self.properties, "provider") and isinstance( + if hasattr(self.properties, "provider") and isinstance( self.properties.provider, DwaveProviderProperties ): edges = self.properties.provider.couplers return from_edgelist(edges, create_using=DiGraph()) - else: - return None + return None @property def _default_shots(self) -> int: @@ -593,9 +587,9 @@ def get_devices( all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. Examples: - >>> AwsDevice.get_devices(provider_names=['Rigetti'], statuses=['ONLINE']) - >>> AwsDevice.get_devices(order_by='provider_name') - >>> AwsDevice.get_devices(types=['SIMULATOR']) + >>> AwsDevice.get_devices(provider_names=["Rigetti"], statuses=["ONLINE"]) + >>> AwsDevice.get_devices(order_by="provider_name") + >>> AwsDevice.get_devices(types=["SIMULATOR"]) Args: arns (Optional[list[str]]): device ARN filter, default is `None` @@ -779,7 +773,7 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: and self.properties.pulse.nativeGateCalibrationsRef ): try: - with urllib.request.urlopen( + with urllib.request.urlopen( # noqa: S310 self.properties.pulse.nativeGateCalibrationsRef.split("?")[0] ) as f: json_calibration_data = self._parse_calibration_json( @@ -795,8 +789,8 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: def _parse_waveforms(self, waveforms_json: dict) -> dict: waveforms = {} - for waveform in waveforms_json: - parsed_waveform = _parse_waveform_from_calibration_schema(waveforms_json[waveform]) + for waveform in waveforms_json.values(): + parsed_waveform = _parse_waveform_from_calibration_schema(waveform) waveforms[parsed_waveform.id] = parsed_waveform return waveforms diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 37eb67603..55e903b1a 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -470,7 +470,7 @@ def metrics( if "startedAt" in metadata: job_start = int(metadata["startedAt"].timestamp()) if self.state() in AwsQuantumJob.TERMINAL_STATES and "endedAt" in metadata: - job_end = int(math.ceil(metadata["endedAt"].timestamp())) + job_end = math.ceil(metadata["endedAt"].timestamp()) return fetcher.get_metrics_for_job( self.name, metric_type, statistic, job_start, job_end, self._logs_prefix ) @@ -515,8 +515,7 @@ def result( except ClientError as e: if e.response["Error"]["Code"] == "404": return {} - else: - raise e + raise return AwsQuantumJob._read_and_deserialize_results(temp_dir, job_name) @staticmethod @@ -561,8 +560,7 @@ def download_result( AwsQuantumJob._attempt_results_download(self, output_s3_uri, output_s3_path) AwsQuantumJob._extract_tar_file(f"{extract_to}/{self.name}") return - else: - time.sleep(poll_interval_seconds) + time.sleep(poll_interval_seconds) raise TimeoutError( f"{job_response['jobName']}: Polling for job completion " @@ -576,7 +574,7 @@ def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) ) except ClientError as e: if e.response["Error"]["Code"] != "404": - raise e + raise exception_response = { "Error": { "Code": "404", @@ -589,7 +587,7 @@ def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) @staticmethod def _extract_tar_file(extract_path: str) -> None: with tarfile.open(AwsQuantumJob.RESULTS_TAR_FILENAME, "r:gz") as tar: - tar.extractall(extract_path) + tar.extractall(extract_path) # noqa: S202 def __repr__(self) -> str: return f"AwsQuantumJob('arn':'{self.arn}')" @@ -623,13 +621,14 @@ def _initialize_regional_device_session( logger.info(f"Changed session region from '{current_region}' to '{device_region}'") try: aws_session.get_device(device) - return aws_session except ClientError as e: raise ( ValueError(f"'{device}' not found.") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e ) from e + else: + return aws_session @staticmethod def _initialize_non_regional_device_session( @@ -638,13 +637,14 @@ def _initialize_non_regional_device_session( original_region = aws_session.region try: aws_session.get_device(device) - return aws_session except ClientError as e: if e.response["Error"]["Code"] != "ResourceNotFoundException": - raise e + raise if "qpu" not in device: raise ValueError(f"Simulator '{device}' not found in '{original_region}'") from e + else: + return aws_session for region in frozenset(AwsDevice.REGIONS) - {original_region}: device_session = aws_session.copy_session(region=region) try: @@ -652,8 +652,9 @@ def _initialize_non_regional_device_session( logger.info( f"Changed session region from '{original_region}' to '{device_session.region}'" ) - return device_session except ClientError as e: if e.response["Error"]["Code"] != "ResourceNotFoundException": - raise e + raise + else: + return device_session raise ValueError(f"QPU '{device}' not found.") diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 4dc4940fb..0f47d18ec 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -14,6 +14,7 @@ from __future__ import annotations import asyncio +import contextlib import time import warnings from functools import singledispatch @@ -93,23 +94,21 @@ class AwsQuantumTask(QuantumTask): def create( aws_session: AwsSession, device_arn: str, - task_specification: Union[ - Circuit, - Problem, - OpenQASMProgram, - BlackbirdProgram, - PulseSequence, - AnalogHamiltonianSimulation, - ], + task_specification: Circuit + | Problem + | OpenQASMProgram + | BlackbirdProgram + | PulseSequence + | AnalogHamiltonianSimulation, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, - device_parameters: dict[str, Any] | None = None, + device_parameters: Optional[dict[str, Any]] = None, disable_qubit_rewiring: bool = False, - tags: dict[str, str] | None = None, - inputs: dict[str, float] | None = None, - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, + tags: Optional[dict[str, str]] = None, + inputs: Optional[dict[str, float]] = None, + gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, quiet: bool = False, - reservation_arn: str | None = None, + reservation_arn: Optional[str] = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -176,7 +175,7 @@ def create( See Also: `braket.aws.aws_quantum_simulator.AwsQuantumSimulator.run()` `braket.aws.aws_qpu.AwsQpu.run()` - """ # noqa E501 + """ # noqa: E501 if len(s3_destination_folder) != 2: raise ValueError( "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." @@ -194,16 +193,14 @@ def create( gate_definitions = gate_definitions or {} if reservation_arn: - create_task_kwargs.update( - { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] - } - ) + create_task_kwargs.update({ + "associations": [ + { + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + }) if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} @@ -229,7 +226,7 @@ def create( def __init__( self, arn: str, - aws_session: AwsSession | None = None, + aws_session: Optional[AwsSession] = None, poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), @@ -252,19 +249,20 @@ def __init__( position. Default is `False`. Examples: - >>> task = AwsQuantumTask(arn='task_arn') + >>> task = AwsQuantumTask(arn="task_arn") >>> task.state() 'COMPLETED' >>> result = task.result() AnnealingQuantumTaskResult(...) - >>> task = AwsQuantumTask(arn='task_arn', poll_timeout_seconds=300) + >>> task = AwsQuantumTask(arn="task_arn", poll_timeout_seconds=300) >>> result = task.result() GateModelQuantumTaskResult(...) """ if kwargs: warnings.warn( - f"AwsQuantumTask.__init__ received unknown keyword args: {list(kwargs.keys())}" + f"AwsQuantumTask.__init__ received unknown keyword args: {list(kwargs.keys())}", + stacklevel=2, ) self._arn: str = arn @@ -278,8 +276,8 @@ def __init__( self._quiet = quiet self._metadata: dict[str, Any] = {} - self._result: Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult + self._result: Optional[ + GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult ] = None @staticmethod @@ -402,9 +400,7 @@ def _update_status_if_nonterminal(self) -> str: def result( self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: + ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: """Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the quantum task is completed, the result is retrieved from S3 and returned as a `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` @@ -417,7 +413,7 @@ def result( Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: The result of the quantum task, if the quantum task completed successfully; returns `None` if the quantum task did not complete successfully or the future timed out. - """ # noqa E501 + """ # noqa: E501 if self._result or ( self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES ): @@ -467,9 +463,7 @@ async def _create_future(self) -> asyncio.Task: async def _wait_for_completion( self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: + ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: """Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: @@ -482,7 +476,7 @@ async def _wait_for_completion( Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. - """ # noqa E501 + """ # noqa: E501 self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() @@ -497,18 +491,17 @@ async def _wait_for_completion( self._logger.debug(f"Task {self._arn}: task status {task_status}") if task_status in AwsQuantumTask.RESULTS_READY_STATES: return self._download_result() - elif task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: + if task_status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: self._result = None return None - else: - await asyncio.sleep(self._poll_interval_seconds) + await asyncio.sleep(self._poll_interval_seconds) # Timed out self._logger.warning( f"Task {self._arn}: polling for task completion timed out after " - + f"{time.time() - start_time} seconds. Please increase the timeout; " - + "this can be done by creating a new AwsQuantumTask with this task's ARN " - + "and a higher value for the `poll_timeout_seconds` parameter." + f"{time.time() - start_time} seconds. Please increase the timeout; " + "this can be done by creating a new AwsQuantumTask with this task's ARN " + "and a higher value for the `poll_timeout_seconds` parameter." ) self._result = None return None @@ -521,9 +514,7 @@ def _has_reservation_arn_from_metadata(self, current_metadata: dict[str, Any]) - def _download_result( self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: + ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: current_metadata = self.metadata(True) result_string = self._aws_session.retrieve_s3_object_body( current_metadata["outputS3Bucket"], @@ -536,12 +527,10 @@ def _download_result( "execution_duration": None, "has_reservation_arn": self._has_reservation_arn_from_metadata(current_metadata), } - try: + with contextlib.suppress(AttributeError): task_event["execution_duration"] = ( self._result.additional_metadata.simulatorMetadata.executionDuration ) - except AttributeError: - pass broadcast_event(_TaskCompletionEvent(**task_event)) return self._result @@ -557,11 +546,11 @@ def __hash__(self) -> int: @singledispatch def _create_internal( - task_specification: Union[Circuit, Problem, BlackbirdProgram], + task_specification: Circuit | Problem | BlackbirdProgram, aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], + device_parameters: Union[dict[str, str], BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -577,7 +566,8 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram + # Not currently used for OpenQasmProgram + _device_parameters: Union[dict[str, str], BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -745,10 +735,7 @@ def _( create_task_kwargs: dict[str, Any], device_arn: str, device_parameters: Union[ - dict, - DwaveDeviceParameters, - DwaveAdvantageDeviceParameters, - Dwave2000QDeviceParameters, + dict, DwaveDeviceParameters, DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters ], _: bool, inputs: dict[str, float], @@ -805,7 +792,7 @@ def _circuit_device_params_from_dict( def _create_annealing_device_params( device_params: dict[str, Any], device_arn: str -) -> Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: +) -> DwaveAdvantageDeviceParameters | Dwave2000QDeviceParameters: """Gets Annealing Device Parameters. Args: @@ -833,15 +820,14 @@ def _create_annealing_device_params( device_level_parameters ) return DwaveAdvantageDeviceParameters(deviceLevelParameters=device_level_parameters) - elif "2000Q" in device_arn: + if "2000Q" in device_arn: device_level_parameters = Dwave2000QDeviceLevelParameters.parse_obj(device_level_parameters) return Dwave2000QDeviceParameters(deviceLevelParameters=device_level_parameters) - else: - raise Exception( - f"Amazon Braket could not find a device with ARN: {device_arn}. " - "To continue, make sure that the value of the device_arn parameter " - "corresponds to a valid QPU." - ) + raise ValueError( + f"Amazon Braket could not find a device with ARN: {device_arn}. " + "To continue, make sure that the value of the device_arn parameter " + "corresponds to a valid QPU." + ) def _create_common_params( @@ -857,8 +843,8 @@ def _create_common_params( @singledispatch def _format_result( - result: Union[GateModelTaskResult, AnnealingTaskResult, PhotonicModelTaskResult], -) -> Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: + result: GateModelTaskResult | AnnealingTaskResult | PhotonicModelTaskResult, +) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: raise TypeError("Invalid result specification type") diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 30c3a8658..106db698a 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -16,7 +16,7 @@ import time from concurrent.futures.thread import ThreadPoolExecutor from itertools import repeat -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any, Optional from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem @@ -57,13 +57,13 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: Union[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - list[ - Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation - ] - ], + task_specifications: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | AnalogHamiltonianSimulation + | list[ + Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, @@ -71,15 +71,14 @@ def __init__( max_workers: int = MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Union[dict[str, float], list[dict[str, float]]] | None = None, + inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, gate_definitions: ( - Union[ - dict[tuple[Gate, QubitSet], PulseSequence], - list[dict[tuple[Gate, QubitSet], PulseSequence]], + Optional[ + dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] ] - | None ) = None, - reservation_arn: str | None = None, + reservation_arn: Optional[str] = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, ): @@ -120,7 +119,7 @@ def __init__( Default: None. *aws_quantum_task_args (Any): Arbitrary args for `QuantumTask`. **aws_quantum_task_kwargs (Any): Arbitrary kwargs for `QuantumTask`., - """ # noqa E501 + """ # noqa: E501 self._tasks = AwsQuantumTaskBatch._execute( aws_session, device_arn, @@ -157,22 +156,22 @@ def __init__( @staticmethod def _tasks_inputs_gatedefs( - task_specifications: Union[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - list[ - Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation - ] - ], + task_specifications: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | AnalogHamiltonianSimulation + | list[ + Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation ], - inputs: Union[dict[str, float], list[dict[str, float]]] = None, - gate_definitions: Union[ - dict[tuple[Gate, QubitSet], PulseSequence], - list[dict[tuple[Gate, QubitSet], PulseSequence]], + inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, + gate_definitions: Optional[ + dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] ] = None, ) -> list[ tuple[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], + Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation, dict[str, float], dict[tuple[Gate, QubitSet], PulseSequence], ] @@ -200,15 +199,14 @@ def _tasks_inputs_gatedefs( arg_lengths.append(arg_length) if arg_length != 1: - if batch_length != 1 and arg_length != batch_length: + if batch_length not in {1, arg_length}: raise ValueError( "Multiple inputs, task specifications and gate definitions must " "be equal in length." ) - else: - batch_length = arg_length + batch_length = arg_length - for i, arg_length in enumerate(arg_lengths): + for i in range(len(arg_lengths)): if isinstance(args[i], (dict, single_task_type)): args[i] = repeat(args[i], batch_length) @@ -219,8 +217,7 @@ def _tasks_inputs_gatedefs( param_names = {param.name for param in task_specification.parameters} if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( - f"Cannot execute circuit with unbound parameters: " - f"{unbounded_parameters}" + f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" ) return tasks_inputs_definitions @@ -229,13 +226,13 @@ def _tasks_inputs_gatedefs( def _execute( aws_session: AwsSession, device_arn: str, - task_specifications: Union[ - Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], - list[ - Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation - ] - ], + task_specifications: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | AnalogHamiltonianSimulation + | list[ + Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation ], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, @@ -243,15 +240,14 @@ def _execute( max_workers: int = MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Union[dict[str, float], list[dict[str, float]]] = None, + inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, gate_definitions: ( - Union[ - dict[tuple[Gate, QubitSet], PulseSequence], - list[dict[tuple[Gate, QubitSet], PulseSequence]], + Optional[ + dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] ] - | None ) = None, - reservation_arn: str | None = None, + reservation_arn: Optional[str] = None, *args, **kwargs, ) -> list[AwsQuantumTask]: @@ -292,22 +288,23 @@ def _execute( remaining.clear() raise - tasks = [future.result() for future in task_futures] - return tasks + return [future.result() for future in task_futures] @staticmethod def _create_task( remaining: list[int], aws_session: AwsSession, device_arn: str, - task_specification: Union[ - Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation - ], + task_specification: Circuit + | Problem + | OpenQasmProgram + | BlackbirdProgram + | AnalogHamiltonianSimulation, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: dict[str, float] = None, - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, + inputs: Optional[dict[str, float]] = None, + gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, reservation_arn: str | None = None, *args, **kwargs, @@ -361,10 +358,10 @@ def results( even when results have already been cached. Default: `True`. Returns: - list[GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult | AnalogHamiltonianSimulationQuantumTaskResult]: The # noqa: E501 + list[GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult | AnalogHamiltonianSimulationQuantumTaskResult]: The results of all of the quantum tasks in the batch. `FAILED`, `CANCELLED`, or timed out quantum tasks will have a result of None - """ + """ # noqa: E501 if not self._results or not use_cached_value: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) self._unsuccessful = { @@ -458,8 +455,8 @@ def unfinished(self) -> set[str]: with ThreadPoolExecutor(max_workers=self._max_workers) as executor: status_futures = {task.id: executor.submit(task.state) for task in self._tasks} unfinished = set() - for task_id in status_futures: - status = status_futures[task_id].result() + for task_id, task_result in status_futures.items(): + status = task_result.result() if status not in AwsQuantumTask.TERMINAL_STATES: unfinished.add(task_id) if status in AwsQuantumTask.NO_RESULT_TERMINAL_STATES: diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 83a690c27..c8bad13e9 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -34,7 +34,7 @@ from braket.tracking.tracking_events import _TaskCreationEvent, _TaskStatusEvent -class AwsSession: +class AwsSession: # noqa: PLR0904 """Manage interactions with AWS services.""" class S3DestinationFolder(NamedTuple): @@ -45,10 +45,10 @@ class S3DestinationFolder(NamedTuple): def __init__( self, - boto_session: boto3.Session | None = None, - braket_client: client | None = None, - config: Config | None = None, - default_bucket: str | None = None, + boto_session: Optional[boto3.Session] = None, + braket_client: Optional[client] = None, + config: Optional[Config] = None, + default_bucket: Optional[str] = None, ): """Initializes an `AwsSession`. @@ -214,7 +214,7 @@ def add_braket_user_agent(self, user_agent: str) -> None: ) @staticmethod - def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> None: + def _add_cost_tracker_count_handler(request: awsrequest.AWSRequest, **kwargs) -> None: # noqa: ARG004 request.headers.add_header("Braket-Trackers", str(len(active_trackers()))) # @@ -253,7 +253,8 @@ def create_quantum_task(self, **boto3_kwargs) -> str: warnings.warn( "A reservation ARN was passed to 'CreateQuantumTask', but it is being overridden " "by a 'DirectReservation' context. If this was not intended, please review your " - "reservation ARN settings or the context in which 'CreateQuantumTask' is called." + "reservation ARN settings or the context in which 'CreateQuantumTask' is called.", + stacklevel=2, ) # Ensure reservation only applies to specific device @@ -297,10 +298,10 @@ def _should_giveup(err: Exception) -> bool: return not ( isinstance(err, ClientError) and err.response["Error"]["Code"] - in [ + in { "ResourceNotFoundException", "ThrottlingException", - ] + } ) @backoff.on_exception( @@ -609,9 +610,8 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) error_code = e.response["Error"]["Code"] message = e.response["Error"]["Message"] - if ( - error_code == "BucketAlreadyOwnedByYou" - or error_code != "BucketAlreadyExists" + if error_code == "BucketAlreadyOwnedByYou" or ( + error_code != "BucketAlreadyExists" and error_code == "OperationAborted" and "conflicting conditional operation" in message ): @@ -717,11 +717,12 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]: r"^[sS]3://([^./]+)/(.+)$", s3_uri ) if s3_uri_match is None: - raise AssertionError + raise AssertionError # noqa: TRY301 bucket, key = s3_uri_match.groups() - return bucket, key except (AssertionError, ValueError) as e: raise ValueError(f"Not a valid S3 uri: {s3_uri}") from e + else: + return bucket, key @staticmethod def construct_s3_uri(bucket: str, *dirs: str) -> str: @@ -855,12 +856,9 @@ def copy_session( region_name=new_region, profile_name=profile_name, ) - copied_session = AwsSession( - boto_session=boto_session, config=config, default_bucket=default_bucket - ) - return copied_session + return AwsSession(boto_session=boto_session, config=config, default_bucket=default_bucket) - @cache + @cache # noqa: B019 def get_full_image_tag(self, image_uri: str) -> str: """Get verbose image tag from image uri. diff --git a/src/braket/aws/direct_reservations.py b/src/braket/aws/direct_reservations.py index 4ffcb8fce..fe7baac82 100644 --- a/src/braket/aws/direct_reservations.py +++ b/src/braket/aws/direct_reservations.py @@ -63,7 +63,8 @@ def __init__(self, device: Device | str | None, reservation_arn: str | None): elif isinstance(device, Device) or device is None: # LocalSimulator warnings.warn( "Using a local simulator with the reservation. For a reservation on a QPU, please " - "ensure the device matches the reserved Braket device." + "ensure the device matches the reserved Braket device.", + stacklevel=2, ) self.device_arn = "" # instead of None, use empty string else: @@ -75,7 +76,7 @@ def __enter__(self): self.start() return self - def __exit__(self, exc_type, exc_val, exc_tb) -> None: + def __exit__(self, exc_type, exc_val, exc_tb) -> None: # noqa: ANN001 self.stop() def start(self) -> None: @@ -91,7 +92,7 @@ def start(self) -> None: def stop(self) -> None: """Stop the reservation context.""" if not DirectReservation._is_active: - warnings.warn("Reservation context is not active.") + warnings.warn("Reservation context is not active.", stacklevel=2) return os.environ.pop("AMZN_BRAKET_RESERVATION_DEVICE_ARN", None) os.environ.pop("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN", None) diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index a5fb52980..0e0f36c14 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -11,15 +11,18 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.circuits import ( # noqa: F401 - circuit, - compiler_directives, - gates, - noises, - observables, - result_types, +from braket.circuits import ( + circuit, # noqa: F401 + compiler_directives, # noqa: F401 + gates, # noqa: F401 + noises, # noqa: F401 + observables, # noqa: F401 + result_types, # noqa: F401 +) +from braket.circuits.angled_gate import ( + AngledGate, # noqa: F401 + DoubleAngledGate, # noqa: F401 ) -from braket.circuits.angled_gate import AngledGate, DoubleAngledGate # noqa: F401 from braket.circuits.circuit import Circuit # noqa: F401 from braket.circuits.circuit_diagram import CircuitDiagram # noqa: F401 from braket.circuits.compiler_directive import CompilerDirective # noqa: F401 @@ -30,16 +33,22 @@ from braket.circuits.instruction import Instruction # noqa: F401 from braket.circuits.moments import Moments, MomentsKey # noqa: F401 from braket.circuits.noise import Noise # noqa: F401 -from braket.circuits.observable import Observable, StandardObservable # noqa: F401 +from braket.circuits.observable import ( + Observable, # noqa: F401 + StandardObservable, # noqa: F401 +) from braket.circuits.operator import Operator # noqa: F401 from braket.circuits.parameterizable import Parameterizable # noqa: F401 from braket.circuits.quantum_operator import QuantumOperator # noqa: F401 from braket.circuits.qubit import Qubit, QubitInput # noqa: F401 from braket.circuits.qubit_set import QubitSet, QubitSetInput # noqa: F401 -from braket.circuits.result_type import ObservableResultType, ResultType # noqa: F401 -from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 - AsciiCircuitDiagram, +from braket.circuits.result_type import ( + ObservableResultType, # noqa: F401 + ResultType, # noqa: F401 +) +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( + AsciiCircuitDiagram, # noqa: F401 ) -from braket.circuits.text_diagram_builders.unicode_circuit_diagram import ( # noqa: F401 - UnicodeCircuitDiagram, +from braket.circuits.text_diagram_builders.unicode_circuit_diagram import ( + UnicodeCircuitDiagram, # noqa: F401 ) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 49447e58f..35f73f984 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -17,7 +17,7 @@ import math from collections.abc import Sequence from functools import singledispatch -from typing import Optional, Union +from typing import Optional from sympy import Float @@ -31,7 +31,7 @@ class AngledGate(Gate, Parameterizable): def __init__( self, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, qubit_count: Optional[int], ascii_symbols: Sequence[str], ): @@ -61,7 +61,7 @@ def __init__( self._parameters = [float(angle)] # explicit casting in case angle is e.g. np.float32 @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameters or bound values. @@ -72,7 +72,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: return self._parameters @property - def angle(self) -> Union[FreeParameterExpression, float]: + def angle(self) -> FreeParameterExpression | float: """Returns the angle of the gate Returns: @@ -128,8 +128,8 @@ class DoubleAngledGate(Gate, Parameterizable): def __init__( self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, qubit_count: Optional[int], ascii_symbols: Sequence[str], ): @@ -165,7 +165,7 @@ def __init__( ] @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameters or bound values. @@ -176,7 +176,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: return self._parameters @property - def angle_1(self) -> Union[FreeParameterExpression, float]: + def angle_1(self) -> FreeParameterExpression | float: """Returns the first angle of the gate Returns: @@ -185,7 +185,7 @@ def angle_1(self) -> Union[FreeParameterExpression, float]: return self._parameters[0] @property - def angle_2(self) -> Union[FreeParameterExpression, float]: + def angle_2(self) -> FreeParameterExpression | float: """Returns the second angle of the gate Returns: @@ -240,9 +240,9 @@ class TripleAngledGate(Gate, Parameterizable): def __init__( self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + angle_3: FreeParameterExpression | float, qubit_count: Optional[int], ascii_symbols: Sequence[str], ): @@ -281,7 +281,7 @@ def __init__( ] @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameters or bound values. @@ -292,7 +292,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, float]]: return self._parameters @property - def angle_1(self) -> Union[FreeParameterExpression, float]: + def angle_1(self) -> FreeParameterExpression | float: """Returns the first angle of the gate Returns: @@ -301,7 +301,7 @@ def angle_1(self) -> Union[FreeParameterExpression, float]: return self._parameters[0] @property - def angle_2(self) -> Union[FreeParameterExpression, float]: + def angle_2(self) -> FreeParameterExpression | float: """Returns the second angle of the gate Returns: @@ -310,7 +310,7 @@ def angle_2(self) -> Union[FreeParameterExpression, float]: return self._parameters[1] @property - def angle_3(self) -> Union[FreeParameterExpression, float]: + def angle_3(self) -> FreeParameterExpression | float: """Returns the third angle of the gate Returns: @@ -361,17 +361,17 @@ def __hash__(self): @singledispatch def _angles_equal( - angle_1: Union[FreeParameterExpression, float], angle_2: Union[FreeParameterExpression, float] + angle_1: FreeParameterExpression | float, angle_2: FreeParameterExpression | float ) -> bool: return isinstance(angle_2, float) and math.isclose(angle_1, angle_2) @_angles_equal.register -def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): +def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): # noqa: FURB118 return angle_1 == angle_2 -def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, float]) -> str: +def angled_ascii_characters(gate: str, angle: FreeParameterExpression | float) -> str: """Generates a formatted ascii representation of an angled gate. Args: @@ -382,12 +382,12 @@ def angled_ascii_characters(gate: str, angle: Union[FreeParameterExpression, flo str: Returns the ascii representation for an angled gate. """ - return f'{gate}({angle:{".2f" if isinstance(angle, (float, Float)) else ""}})' + return f"{gate}({angle:{'.2f' if isinstance(angle, (float, Float)) else ''}})" def _multi_angled_ascii_characters( gate: str, - *angles: Union[FreeParameterExpression, float], + *angles: FreeParameterExpression | float, ) -> str: """Generates a formatted ascii representation of an angled gate. diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index accbce161..217668385 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -13,6 +13,6 @@ # Moving ascii_circuit_diagram.py into the text_diagram_builders folder in order # to group all classes that print circuits in a text format. -from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 - AsciiCircuitDiagram, +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( + AsciiCircuitDiagram, # noqa: F401 ) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index e3181a603..e9a80dbcd 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -16,7 +16,7 @@ from collections import Counter from collections.abc import Callable, Iterable from numbers import Number -from typing import Any, Optional, TypeVar, Union +from typing import Any, TypeVar import numpy as np import oqpy @@ -72,7 +72,7 @@ AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) -class Circuit: +class Circuit: # noqa: PLR0904 """A representation of a quantum circuit that contains the instructions to be performed on a quantum device and the requested result types. @@ -100,17 +100,15 @@ def register_subroutine(cls, func: SubroutineCallable) -> None: ... for qubit in target: ... circ += Instruction(Gate.H(), qubit) ... return circ - ... >>> Circuit.register_subroutine(h_on_all) >>> circ = Circuit().h_on_all(range(2)) >>> for instr in circ.instructions: ... print(instr) - ... Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) Instruction('operator': 'H', 'target': QubitSet(Qubit(1),)) """ - def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: + def method_from_subroutine(self, *args, **kwargs) -> SubroutineReturn: # noqa: ANN001 return self.add(func, *args, **kwargs) function_name = func.__name__ @@ -137,14 +135,13 @@ def __init__(self, addable: AddableTypes | None = None, *args, **kwargs): >>> @circuit.subroutine(register=True) >>> def bell_pair(target): ... return Circ().h(target[0]).cnot(target[0:2]) - ... - >>> circ = Circuit(bell_pair, [4,5]) - >>> circ = Circuit().bell_pair([4,5]) + >>> circ = Circuit(bell_pair, [4, 5]) + >>> circ = Circuit().bell_pair([4, 5]) """ self._moments: Moments = Moments() self._result_types: dict[ResultType] = {} - self._qubit_observable_mapping: dict[Union[int, Circuit._ALL_QUBITS], Observable] = {} + self._qubit_observable_mapping: dict[int | Circuit._ALL_QUBITS, Observable] = {} self._qubit_observable_target_mapping: dict[int, tuple[int]] = {} self._qubit_observable_set = set() self._parameters = set() @@ -338,13 +335,12 @@ def add_result_type( return self @staticmethod - def _extract_observable(result_type: ResultType) -> Optional[Observable]: + def _extract_observable(result_type: ResultType) -> Observable | None: if isinstance(result_type, ResultType.Probability): return Observable.Z() # computational basis - elif isinstance(result_type, ObservableResultType): + if isinstance(result_type, ObservableResultType): return result_type.observable - else: - return None + return None def _add_to_qubit_observable_mapping( self, observable: Observable, observable_target: QubitSet @@ -366,9 +362,9 @@ def _add_to_qubit_observable_mapping( current_observable == identity and new_observable != identity ) if ( - not add_observable - and current_observable != identity - and new_observable != identity + not add_observable # noqa: PLR1714 + and identity != current_observable + and identity != new_observable and current_observable != new_observable ): return self._encounter_noncommuting_observable() @@ -390,6 +386,7 @@ def _add_to_qubit_observable_mapping( if all_qubits_observable and all_qubits_observable != observable: return self._encounter_noncommuting_observable() self._qubit_observable_mapping[Circuit._ALL_QUBITS] = observable + return None @staticmethod def _tensor_product_index_dict( @@ -612,7 +609,7 @@ def add_circuit( """ if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") - elif target is not None: + if target is not None: keys = sorted(circuit.qubits) values = target target_mapping = dict(zip(keys, values)) @@ -678,7 +675,7 @@ def add_verbatim_box( """ if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") - elif target is not None: + if target is not None: keys = sorted(verbatim_circuit.qubits) values = target target_mapping = dict(zip(keys, values)) @@ -760,10 +757,10 @@ def measure(self, target_qubits: QubitSetInput) -> Circuit: def apply_gate_noise( self, - noise: Union[type[Noise], Iterable[type[Noise]]], - target_gates: Optional[Union[type[Gate], Iterable[type[Gate]]]] = None, - target_unitary: Optional[np.ndarray] = None, - target_qubits: Optional[QubitSetInput] = None, + noise: type[Noise] | Iterable[type[Noise]], + target_gates: type[Gate] | Iterable[type[Gate]] | None = None, + target_unitary: np.ndarray | None = None, + target_qubits: QubitSetInput | None = None, ) -> Circuit: """Apply `noise` to the circuit according to `target_gates`, `target_unitary` and `target_qubits`. @@ -903,13 +900,12 @@ def apply_gate_noise( if target_unitary is not None: return apply_noise_to_gates(self, noise, target_unitary, target_qubits) - else: - return apply_noise_to_gates(self, noise, target_gates, target_qubits) + return apply_noise_to_gates(self, noise, target_gates, target_qubits) def apply_initialization_noise( self, - noise: Union[type[Noise], Iterable[type[Noise]]], - target_qubits: Optional[QubitSetInput] = None, + noise: type[Noise] | Iterable[type[Noise]], + target_qubits: QubitSetInput | None = None, ) -> Circuit: """Apply `noise` at the beginning of the circuit for every qubit (default) or target_qubits`. @@ -939,18 +935,18 @@ def apply_initialization_noise( not the same as `noise.qubit_count`. Examples: - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) >>> print(circ) >>> noise = Noise.Depolarizing(probability=0.1) - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) >>> print(circ.apply_initialization_noise(noise)) - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ.apply_initialization_noise(noise, target_qubits = 1)) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) + >>> print(circ.apply_initialization_noise(noise, target_qubits=1)) >>> circ = Circuit() - >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) + >>> print(circ.apply_initialization_noise(noise, target_qubits=[0, 1])) """ if (len(self.qubits) == 0) and (target_qubits is None): @@ -1047,14 +1043,14 @@ def _validate_parameter_value(val: Any) -> None: ValueError: If the value is not a Number """ if not isinstance(val, Number): - raise ValueError( + raise TypeError( f"Parameters can only be assigned numeric values. Invalid inputs: {val}" ) def apply_readout_noise( self, - noise: Union[type[Noise], Iterable[type[Noise]]], - target_qubits: Optional[QubitSetInput] = None, + noise: type[Noise] | Iterable[type[Noise]], + target_qubits: QubitSetInput | None = None, ) -> Circuit: """Apply `noise` right before measurement in every qubit (default) or target_qubits`. @@ -1084,18 +1080,18 @@ def apply_readout_noise( not the same as `noise.qubit_count`. Examples: - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) >>> print(circ) >>> noise = Noise.Depolarizing(probability=0.1) - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) >>> print(circ.apply_initialization_noise(noise)) - >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0,1) - >>> print(circ.apply_initialization_noise(noise, target_qubits = 1)) + >>> circ = Circuit().x(0).y(1).z(0).x(1).cnot(0, 1) + >>> print(circ.apply_initialization_noise(noise, target_qubits=1)) >>> circ = Circuit() - >>> print(circ.apply_initialization_noise(noise, target_qubits = [0, 1])) + >>> print(circ.apply_initialization_noise(noise, target_qubits=[0, 1])) """ if (len(self.qubits) == 0) and (target_qubits is None): @@ -1160,12 +1156,11 @@ def add(self, addable: AddableTypes, *args, **kwargs) -> Circuit: >>> @circuit.subroutine() >>> def bell_pair(target): - ... return Circuit().h(target[0]).cnot(target[0: 2]) - ... - >>> circ = Circuit().add(bell_pair, [4,5]) + ... return Circuit().h(target[0]).cnot(target[0:2]) + >>> circ = Circuit().add(bell_pair, [4, 5]) """ - def _flatten(addable: Union[Iterable, AddableTypes]) -> AddableTypes: + def _flatten(addable: Iterable | AddableTypes) -> AddableTypes: if isinstance(addable, Iterable): for item in addable: yield from _flatten(item) @@ -1219,7 +1214,7 @@ def to_ir( ir_type: IRType = IRType.JAQCD, serialization_properties: SerializationProperties | None = None, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, - ) -> Union[OpenQasmProgram, JaqcdProgram]: + ) -> OpenQasmProgram | JaqcdProgram: """Converts the circuit into the canonical intermediate representation. If the circuit is sent over the wire, this method is called before it is sent. @@ -1244,7 +1239,7 @@ def to_ir( gate_definitions = gate_definitions or {} if ir_type == IRType.JAQCD: return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): @@ -1256,13 +1251,10 @@ def to_ir( serialization_properties or OpenQASMSerializationProperties(), gate_definitions.copy(), ) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @staticmethod - def from_ir( - source: Union[str, OpenQasmProgram], inputs: Optional[dict[str, io_type]] = None - ) -> Circuit: + def from_ir(source: str | OpenQasmProgram, inputs: dict[str, io_type] | None = None) -> Circuit: """Converts an OpenQASM program to a Braket Circuit object. Args: @@ -1278,7 +1270,7 @@ def from_ir( inputs_copy.update(inputs) inputs = inputs_copy source = source.source - from braket.circuits.braket_program_context import BraketProgramContext + from braket.circuits.braket_program_context import BraketProgramContext # noqa: PLC0415 return Interpreter(BraketProgramContext()).build_circuit( source=source, @@ -1306,24 +1298,20 @@ def _to_openqasm( ) -> OpenQasmProgram: ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) openqasm_ir_type = IRType.OPENQASM - ir_instructions.extend( - [ - instruction.to_ir( - ir_type=openqasm_ir_type, serialization_properties=serialization_properties - ) - for instruction in self.instructions - ] - ) + ir_instructions.extend([ + instruction.to_ir( + ir_type=openqasm_ir_type, serialization_properties=serialization_properties + ) + for instruction in self.instructions + ]) if self.result_types: - ir_instructions.extend( - [ - result_type.to_ir( - ir_type=openqasm_ir_type, serialization_properties=serialization_properties - ) - for result_type in self.result_types - ] - ) + ir_instructions.extend([ + result_type.to_ir( + ir_type=openqasm_ir_type, serialization_properties=serialization_properties + ) + for result_type in self.result_types + ]) # measure all the qubits if a measure instruction is not provided elif self._measure_targets is None: qubits = ( @@ -1443,7 +1431,7 @@ def _generate_frame_wf_defcal_declarations( def _get_frames_waveforms_from_instrs( self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] ) -> tuple[dict[str, Frame], dict[str, Waveform]]: - from braket.circuits.gates import PulseGate + from braket.circuits.gates import PulseGate # noqa: PLC0415 frames = {} waveforms = {} @@ -1507,11 +1495,11 @@ def _add_fixed_argument_calibrations( ) if free_parameter_number == 0: continue - elif free_parameter_number < len(gate.parameters): + if free_parameter_number < len(gate.parameters): raise NotImplementedError( "Calibrations with a partial number of fixed parameters are not supported." ) - elif any( + if any( isinstance(p, FreeParameterExpression) for p in instruction.operator.parameters ): raise NotImplementedError( @@ -1521,12 +1509,10 @@ def _add_fixed_argument_calibrations( type(instruction.operator)(*instruction.operator.parameters), instruction.target, ) - additional_calibrations[bound_key] = calibration( - **{ - p.name if isinstance(p, FreeParameterExpression) else p: v - for p, v in zip(gate.parameters, instruction.operator.parameters) - } - ) + additional_calibrations[bound_key] = calibration(**{ + p.name if isinstance(p, FreeParameterExpression) else p: v + for p, v in zip(gate.parameters, instruction.operator.parameters) + }) return additional_calibrations def to_unitary(self) -> np.ndarray: @@ -1559,8 +1545,7 @@ def to_unitary(self) -> np.ndarray: """ if qubits := self.qubits: return calculate_unitary_big_endian(self.instructions, qubits) - else: - return np.zeros(0, dtype=complex) + return np.zeros(0, dtype=complex) @property def qubits_frozen(self) -> bool: @@ -1611,11 +1596,7 @@ def __add__(self, addable: AddableTypes) -> Circuit: def __repr__(self) -> str: if not self.result_types: return f"Circuit('instructions': {self.instructions})" - else: - return ( - f"Circuit('instructions': {self.instructions}" - + f", 'result_types': {self.result_types})" - ) + return f"Circuit('instructions': {self.instructions}, 'result_types': {self.result_types})" def __str__(self): return self.diagram() @@ -1661,11 +1642,9 @@ def subroutine(register: bool = False) -> Callable: >>> @circuit.subroutine(register=True) >>> def bell_circuit(): ... return Circuit().h(0).cnot(0, 1) - ... >>> circ = Circuit().bell_circuit() >>> for instr in circ.instructions: ... print(instr) - ... Instruction('operator': 'H', 'target': QubitSet(Qubit(0),)) Instruction('operator': 'H', 'target': QubitSet(Qubit(1),)) """ diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 1a50c4c83..933a62fda 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -35,13 +35,13 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: raise ValueError( "No result types specified for circuit and shots=0. See `braket.circuits.result_types`" ) - elif shots and circuit.result_types: + if shots and circuit.result_types: if not circuit.observables_simultaneously_measurable: raise ValueError("Observables cannot be sampled simultaneously") for rt in circuit.result_types: if isinstance(rt, (ResultType.Amplitude, ResultType.StateVector)): - raise ValueError("StateVector or Amplitude cannot be specified when shots>0") - elif isinstance(rt, ResultType.Probability): + raise ValueError("StateVector or Amplitude cannot be specified when shots>0") # noqa: TRY004 + if isinstance(rt, ResultType.Probability): num_qubits = len(rt.target) or circuit.qubit_count if num_qubits > 40: raise ValueError("Probability target must be less than or equal to 40 qubits.") diff --git a/src/braket/circuits/compiler_directive.py b/src/braket/circuits/compiler_directive.py index ad2c701c6..bd961c3ee 100644 --- a/src/braket/circuits/compiler_directive.py +++ b/src/braket/circuits/compiler_directive.py @@ -72,10 +72,9 @@ def to_ir( """ if ir_type == IRType.JAQCD: return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: return self._to_openqasm() - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self) -> Any: """Returns the JAQCD representation of the compiler directive.""" diff --git a/src/braket/circuits/gate.py b/src/braket/circuits/gate.py index 453b121fd..421e3614a 100644 --- a/src/braket/circuits/gate.py +++ b/src/braket/circuits/gate.py @@ -15,7 +15,7 @@ from collections.abc import Sequence from itertools import groupby -from typing import Any, Optional +from typing import Any from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.quantum_operator import QuantumOperator @@ -33,7 +33,7 @@ class Gate(QuantumOperator): the metadata that defines what a gate is and what it does. """ - def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): + def __init__(self, qubit_count: int | None, ascii_symbols: Sequence[str]): """Initializes a `Gate`. Args: @@ -49,12 +49,12 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or `ascii_symbols` length != `qubit_count` """ - # todo: implement ascii symbols for control modifier + # TODO: implement ascii symbols for control modifier super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) @property def _qasm_name(self) -> NotImplementedError: - raise NotImplementedError() + raise NotImplementedError def adjoint(self) -> list[Gate]: """Returns a list of gates that implement the adjoint of this gate. @@ -70,10 +70,10 @@ def to_ir( self, target: QubitSet, ir_type: IRType = IRType.JAQCD, - serialization_properties: Optional[SerializationProperties] = None, + serialization_properties: SerializationProperties | None = None, *, - control: Optional[QubitSet] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSet | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Any: r"""Returns IR object of quantum operator and target @@ -110,7 +110,7 @@ def to_ir( if control or power != 1: raise ValueError("Gate modifiers are not supported with Jaqcd.") return self._to_jaqcd(target) - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): @@ -125,8 +125,7 @@ def to_ir( control_state=control_state, power=power, ) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self, target: QubitSet) -> Any: """Returns the JAQCD representation of the gate. @@ -144,8 +143,8 @@ def _to_openqasm( target: QubitSet, serialization_properties: OpenQASMSerializationProperties, *, - control: Optional[QubitSet] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSet | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> str: """Returns the OpenQASM string representation of the gate. diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 69ff66254..2d855cabf 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -27,10 +27,10 @@ class GateCalibrations: - """An object containing gate calibration data. The data represents the mapping on a particular gate - on a set of qubits to its calibration to be used by a quantum device. This is represented by a dictionary - with keys of `Tuple(Gate, QubitSet)` mapped to a `PulseSequence`. - """ # noqa: E501 + """An object containing gate calibration data. The data represents the mapping on a particular + gate on a set of qubits to its calibration to be used by a quantum device. This is represented + by a dictionary with keys of `Tuple(Gate, QubitSet)` mapped to a `PulseSequence`. + """ def __init__( self, @@ -119,8 +119,8 @@ def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: """Returns the defcal representation for the `GateCalibrations` object. Args: - calibration_key (tuple[Gate, QubitSet] | None): An optional key to get a specific defcal. - Default: None + calibration_key (tuple[Gate, QubitSet] | None): An optional key to get a + specific defcal. Default: None Raises: ValueError: Key does not exist in the `GateCalibrations` object. @@ -128,9 +128,9 @@ def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: Returns: str: the defcal string for the object. - """ # noqa: E501 + """ if calibration_key is not None: - if calibration_key not in self.pulse_sequences.keys(): + if calibration_key not in self.pulse_sequences: raise ValueError( f"The key {calibration_key} does not exist in this GateCalibrations object." ) @@ -139,26 +139,22 @@ def to_ir(self, calibration_key: tuple[Gate, QubitSet] | None = None) -> str: .to_ir() .replace("cal", self._def_cal_gate(calibration_key), 1) ) - else: - defcal = "\n".join( - v.to_ir().replace("cal", self._def_cal_gate(k), 1) - for (k, v) in self.pulse_sequences.items() - ) - return defcal + return "\n".join( + v.to_ir().replace("cal", self._def_cal_gate(k), 1) + for (k, v) in self.pulse_sequences.items() + ) def _def_cal_gate(self, gate_key: tuple[Gate, QubitSet]) -> str: - return " ".join( - [ - "defcal", - gate_key[0].to_ir( - target=gate_key[1], - serialization_properties=OpenQASMSerializationProperties( - QubitReferenceType.PHYSICAL - ), - ir_type=IRType.OPENQASM, - )[:-1], - ] - ) + return " ".join([ + "defcal", + gate_key[0].to_ir( + target=gate_key[1], + serialization_properties=OpenQASMSerializationProperties( + QubitReferenceType.PHYSICAL + ), + ir_type=IRType.OPENQASM, + )[:-1], + ]) def __eq__(self, other: GateCalibrations): return isinstance(other, GateCalibrations) and other.pulse_sequences == self.pulse_sequences diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index ee5ea684b..954f5c275 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -15,7 +15,7 @@ from collections.abc import Iterable from copy import deepcopy -from typing import Any, Optional, Union +from typing import Any import numpy as np from oqpy import Program @@ -95,8 +95,8 @@ def fixed_qubit_count() -> int: def h( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Hadamard gate. @@ -173,8 +173,8 @@ def fixed_qubit_count() -> int: def i( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Identity gate. @@ -231,7 +231,7 @@ class GPhase(AngledGate): ValueError: If `angle` is not present """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): # Avoid parent constructor because _qubit_count must be zero self._qubit_count = self.fixed_qubit_count() self._ascii_symbols = [] @@ -263,10 +263,10 @@ def fixed_qubit_count() -> int: @staticmethod @circuit.subroutine(register=True) def gphase( - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction | Iterable[Instruction]: r"""Global phase gate. @@ -369,8 +369,8 @@ def fixed_qubit_count() -> int: def x( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Pauli-X gate. @@ -449,8 +449,8 @@ def fixed_qubit_count() -> int: def y( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Pauli-Y gate. @@ -529,8 +529,8 @@ def fixed_qubit_count() -> int: def z( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Pauli-Z gate. @@ -607,8 +607,8 @@ def fixed_qubit_count() -> int: def s( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""S gate. @@ -685,8 +685,8 @@ def fixed_qubit_count() -> int: def si( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Conjugate transpose of S gate. @@ -763,8 +763,8 @@ def fixed_qubit_count() -> int: def t( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""T gate. @@ -841,8 +841,8 @@ def fixed_qubit_count() -> int: def ti( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Conjugate transpose of T gate. @@ -919,8 +919,8 @@ def fixed_qubit_count() -> int: def v( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Square root of X gate (V gate). @@ -997,8 +997,8 @@ def fixed_qubit_count() -> int: def vi( target: QubitSetInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Conjugate transpose of square root of X gate (conjugate transpose of V). @@ -1056,7 +1056,7 @@ class Rx(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1091,10 +1091,10 @@ def bind_values(self, **kwargs) -> AngledGate: @circuit.subroutine(register=True) def rx( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""X-axis rotation gate. @@ -1149,7 +1149,7 @@ class Ry(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1184,10 +1184,10 @@ def bind_values(self, **kwargs) -> AngledGate: @circuit.subroutine(register=True) def ry( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Y-axis rotation gate. @@ -1243,7 +1243,7 @@ class Rz(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1273,10 +1273,10 @@ def fixed_qubit_count() -> int: @circuit.subroutine(register=True) def rz( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Z-axis rotation gate. @@ -1331,7 +1331,7 @@ class PhaseShift(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1359,10 +1359,10 @@ def fixed_qubit_count() -> int: @circuit.subroutine(register=True) def phaseshift( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Phase shift gate. @@ -1425,9 +1425,9 @@ class U(TripleAngledGate): def __init__( self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + angle_3: FreeParameterExpression | float, ): super().__init__( angle_1=angle_1, @@ -1447,21 +1447,19 @@ def to_matrix(self) -> np.ndarray: Returns: np.ndarray: The matrix representation of this gate. """ - _theta = self.angle_1 - _phi = self.angle_2 - _lambda = self.angle_3 - return np.array( + theta_ = self.angle_1 + phi_ = self.angle_2 + lambda_ = self.angle_3 + return np.array([ [ - [ - np.cos(_theta / 2), - -np.exp(1j * _lambda) * np.sin(_theta / 2), - ], - [ - np.exp(1j * _phi) * np.sin(_theta / 2), - np.exp(1j * (_phi + _lambda)) * np.cos(_theta / 2), - ], - ] - ) + np.cos(theta_ / 2), + -np.exp(1j * lambda_) * np.sin(theta_ / 2), + ], + [ + np.exp(1j * phi_) * np.sin(theta_ / 2), + np.exp(1j * (phi_ + lambda_)) * np.cos(theta_ / 2), + ], + ]) def adjoint(self) -> list[Gate]: return [U(-self.angle_1, -self.angle_3, -self.angle_2)] @@ -1477,12 +1475,12 @@ def bind_values(self, **kwargs) -> TripleAngledGate: @circuit.subroutine(register=True) def u( target: QubitSetInput, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + angle_3: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""Generalized single-qubit rotation gate. @@ -1658,8 +1656,8 @@ def swap( target1: QubitInput, target2: QubitInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""Swap gate. @@ -1750,8 +1748,8 @@ def iswap( target1: QubitInput, target2: QubitInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""ISwap gate. @@ -1811,7 +1809,7 @@ class PSwap(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1851,10 +1849,10 @@ def fixed_qubit_count() -> int: def pswap( target1: QubitInput, target2: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""PSwap gate. @@ -1918,7 +1916,7 @@ class XY(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -1965,10 +1963,10 @@ def fixed_qubit_count() -> int: def xy( target1: QubitInput, target2: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""XY gate. @@ -2029,7 +2027,7 @@ class CPhaseShift(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2058,7 +2056,7 @@ def fixed_qubit_count() -> int: def cphaseshift( control: QubitSetInput, target: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, power: float = 1, ) -> Instruction: r"""Controlled phase shift gate. @@ -2114,7 +2112,7 @@ class CPhaseShift00(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2143,7 +2141,7 @@ def fixed_qubit_count() -> int: def cphaseshift00( control: QubitSetInput, target: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, power: float = 1, ) -> Instruction: r"""Controlled phase shift gate for phasing the \|00> state. @@ -2199,7 +2197,7 @@ class CPhaseShift01(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2228,7 +2226,7 @@ def fixed_qubit_count() -> int: def cphaseshift01( control: QubitSetInput, target: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, power: float = 1, ) -> Instruction: r"""Controlled phase shift gate for phasing the \|01> state. @@ -2284,7 +2282,7 @@ class CPhaseShift10(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2313,7 +2311,7 @@ def fixed_qubit_count() -> int: def cphaseshift10( control: QubitSetInput, target: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, power: float = 1, ) -> Instruction: r"""Controlled phase shift gate for phasing the \\|10> state. @@ -2622,8 +2620,8 @@ def ecr( target1: QubitInput, target2: QubitInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""An echoed RZX(pi/2) gate (ECR gate). @@ -2685,7 +2683,7 @@ class XX(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2732,10 +2730,10 @@ def fixed_qubit_count() -> int: def xx( target1: QubitInput, target2: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""Ising XX coupling gate. @@ -2798,7 +2796,7 @@ class YY(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2845,10 +2843,10 @@ def fixed_qubit_count() -> int: def yy( target1: QubitInput, target2: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""Ising YY coupling gate. @@ -2911,7 +2909,7 @@ class ZZ(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -2951,10 +2949,10 @@ def fixed_qubit_count() -> int: def zz( target1: QubitInput, target2: QubitInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""Ising ZZ coupling gate. @@ -3058,8 +3056,8 @@ def ccnot( control2: QubitInput, target: QubitInput, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""CCNOT gate or Toffoli gate. @@ -3222,7 +3220,7 @@ class GPi(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -3234,12 +3232,10 @@ def _qasm_name(self) -> str: return "gpi" def to_matrix(self) -> np.ndarray: - return np.array( - [ - [0, np.exp(-1j * self.angle)], - [np.exp(1j * self.angle), 0], - ] - ) + return np.array([ + [0, np.exp(-1j * self.angle)], + [np.exp(1j * self.angle), 0], + ]) def adjoint(self) -> list[Gate]: return [GPi(self.angle)] @@ -3255,10 +3251,10 @@ def bind_values(self, **kwargs) -> GPi: @circuit.subroutine(register=True) def gpi( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""IonQ GPi gate. @@ -3318,8 +3314,8 @@ class PRx(DoubleAngledGate): def __init__( self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, ): super().__init__( angle_1=angle_1, @@ -3340,18 +3336,16 @@ def to_matrix(self) -> np.ndarray: """ theta = self.angle_1 phi = self.angle_2 - return np.array( + return np.array([ [ - [ - np.cos(theta / 2), - -1j * np.exp(-1j * phi) * np.sin(theta / 2), - ], - [ - -1j * np.exp(1j * phi) * np.sin(theta / 2), - np.cos(theta / 2), - ], - ] - ) + np.cos(theta / 2), + -1j * np.exp(-1j * phi) * np.sin(theta / 2), + ], + [ + -1j * np.exp(1j * phi) * np.sin(theta / 2), + np.cos(theta / 2), + ], + ]) def adjoint(self) -> list[Gate]: return [PRx(-self.angle_1, self.angle_2)] @@ -3367,11 +3361,11 @@ def bind_values(self, **kwargs) -> PRx: @circuit.subroutine(register=True) def prx( target: QubitSetInput, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""PhaseRx gate. @@ -3431,7 +3425,7 @@ class GPi2(AngledGate): angle (Union[FreeParameterExpression, float]): angle in radians. """ - def __init__(self, angle: Union[FreeParameterExpression, float]): + def __init__(self, angle: FreeParameterExpression | float): super().__init__( angle=angle, qubit_count=None, @@ -3443,12 +3437,10 @@ def _qasm_name(self) -> str: return "gpi2" def to_matrix(self) -> np.ndarray: - return np.array( - [ - [1, -1j * np.exp(-1j * self.angle)], - [-1j * np.exp(1j * self.angle), 1], - ] - ) / np.sqrt(2) + return np.array([ + [1, -1j * np.exp(-1j * self.angle)], + [-1j * np.exp(1j * self.angle), 1], + ]) / np.sqrt(2) def adjoint(self) -> list[Gate]: return [GPi2(self.angle + np.pi)] @@ -3464,10 +3456,10 @@ def bind_values(self, **kwargs) -> GPi2: @circuit.subroutine(register=True) def gpi2( target: QubitSetInput, - angle: Union[FreeParameterExpression, float], + angle: FreeParameterExpression | float, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""IonQ GPi2 gate. @@ -3533,9 +3525,9 @@ class MS(TripleAngledGate): def __init__( self, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float] = np.pi / 2, + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + angle_3: FreeParameterExpression | float = np.pi / 2, ): super().__init__( angle_1=angle_1, @@ -3550,34 +3542,32 @@ def _qasm_name(self) -> str: return "ms" def to_matrix(self) -> np.ndarray: - return np.array( + return np.array([ [ - [ - np.cos(self.angle_3 / 2), - 0, - 0, - -1j * np.exp(-1j * (self.angle_1 + self.angle_2)) * np.sin(self.angle_3 / 2), - ], - [ - 0, - np.cos(self.angle_3 / 2), - -1j * np.exp(-1j * (self.angle_1 - self.angle_2)) * np.sin(self.angle_3 / 2), - 0, - ], - [ - 0, - -1j * np.exp(1j * (self.angle_1 - self.angle_2)) * np.sin(self.angle_3 / 2), - np.cos(self.angle_3 / 2), - 0, - ], - [ - -1j * np.exp(1j * (self.angle_1 + self.angle_2)) * np.sin(self.angle_3 / 2), - 0, - 0, - np.cos(self.angle_3 / 2), - ], - ] - ) + np.cos(self.angle_3 / 2), + 0, + 0, + -1j * np.exp(-1j * (self.angle_1 + self.angle_2)) * np.sin(self.angle_3 / 2), + ], + [ + 0, + np.cos(self.angle_3 / 2), + -1j * np.exp(-1j * (self.angle_1 - self.angle_2)) * np.sin(self.angle_3 / 2), + 0, + ], + [ + 0, + -1j * np.exp(1j * (self.angle_1 - self.angle_2)) * np.sin(self.angle_3 / 2), + np.cos(self.angle_3 / 2), + 0, + ], + [ + -1j * np.exp(1j * (self.angle_1 + self.angle_2)) * np.sin(self.angle_3 / 2), + 0, + 0, + np.cos(self.angle_3 / 2), + ], + ]) def adjoint(self) -> list[Gate]: return [MS(self.angle_1 + np.pi, self.angle_2, self.angle_3)] @@ -3594,12 +3584,12 @@ def bind_values(self, **kwargs) -> MS: def ms( target1: QubitInput, target2: QubitInput, - angle_1: Union[FreeParameterExpression, float], - angle_2: Union[FreeParameterExpression, float], - angle_3: Union[FreeParameterExpression, float] = np.pi / 2, + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + angle_3: FreeParameterExpression | float = np.pi / 2, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Iterable[Instruction]: r"""IonQ Mølmer-Sørensen gate. @@ -3695,7 +3685,7 @@ def _to_openqasm( formatted_matrix = np.array2string( self._matrix, separator=", ", - formatter={"all": lambda x: format_complex(x)}, + formatter={"all": format_complex}, threshold=float("inf"), ).replace("\n", "") @@ -3732,9 +3722,9 @@ def unitary(targets: QubitSet, matrix: np.ndarray, display_name: str = "U") -> I or is not unitary, Examples: - >>> circ = Circuit().unitary(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) + >>> circ = Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]) """ - # todo: handle controlled unitary + # TODO: handle controlled unitary if 2 ** len(targets) != matrix.shape[0]: raise ValueError("Dimensions of the supplied unitary are incompatible with the targets") @@ -3804,8 +3794,8 @@ def pulse_gate( pulse_sequence: PulseSequence, display_name: str = "PG", *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: r"""Arbitrary pulse gate which provides the ability to embed custom pulse sequences @@ -3861,7 +3851,6 @@ def format_complex(number: complex) -> str: return f"{number.real}" imag_sign = "+" if number.imag > 0 else "-" return f"{number.real} {imag_sign} {abs(number.imag)}im" - elif number.imag: + if number.imag: return f"{number.imag}im" - else: - return "0" + return "0" diff --git a/src/braket/circuits/instruction.py b/src/braket/circuits/instruction.py index bedfd0c44..8d998939e 100644 --- a/src/braket/circuits/instruction.py +++ b/src/braket/circuits/instruction.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any from braket.circuits.basis_state import BasisState, BasisStateInput from braket.circuits.compiler_directive import CompilerDirective @@ -36,10 +36,10 @@ class Instruction: def __init__( self, operator: InstructionOperator, - target: Optional[QubitSetInput] = None, + target: QubitSetInput | None = None, *, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: """InstructionOperator includes objects of type `Gate` and `Noise` only. @@ -149,7 +149,7 @@ def adjoint(self) -> list[Instruction]: ) for gate in operator.adjoint() ] - elif isinstance(operator, CompilerDirective): + if isinstance(operator, CompilerDirective): return [Instruction(operator.counterpart(), self._target)] raise NotImplementedError(f"Adjoint not supported for {operator}") @@ -191,11 +191,11 @@ def ascii_symbols(self) -> tuple[str, ...]: def copy( self, - target_mapping: Optional[dict[QubitInput, QubitInput]] = None, - target: Optional[QubitSetInput] = None, - control_mapping: Optional[dict[QubitInput, QubitInput]] = None, - control: Optional[QubitSetInput] = None, - control_state: Optional[BasisStateInput] = None, + target_mapping: dict[QubitInput, QubitInput] | None = None, + target: QubitSetInput | None = None, + control_mapping: dict[QubitInput, QubitInput] | None = None, + control: QubitSetInput | None = None, + control_state: BasisStateInput | None = None, power: float = 1, ) -> Instruction: """Return a shallow copy of the instruction. @@ -287,7 +287,7 @@ def __eq__(self, other: Instruction): ) return NotImplemented - def __pow__(self, power: float, modulo: float = None): + def __pow__(self, power: float, modulo: float | None = None): new_power = self.power * power if modulo is not None: new_power %= modulo diff --git a/src/braket/circuits/measure.py b/src/braket/circuits/measure.py index d31555ba9..091bf134f 100644 --- a/src/braket/circuits/measure.py +++ b/src/braket/circuits/measure.py @@ -67,12 +67,11 @@ def to_ir( """ if ir_type == IRType.JAQCD: return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: return self._to_openqasm( target, serialization_properties or OpenQASMSerializationProperties() ** kwargs ) - else: - raise ValueError(f"supplied ir_type {ir_type} is not supported.") + raise ValueError(f"supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self) -> Any: """Returns the JAQCD representation of the measure.""" diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index b2dee4151..10f236366 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -16,7 +16,7 @@ from collections import OrderedDict from collections.abc import ItemsView, Iterable, KeysView, Mapping, ValuesView from enum import Enum -from typing import Any, NamedTuple, Union +from typing import Any, NamedTuple from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate @@ -90,7 +90,6 @@ class Moments(Mapping[MomentsKey, Instruction]): ... print(f"Item {i}") ... print(f"\\tKey: {item[0]}") ... print(f"\\tValue: {item[1]}") - ... Item 0 Key: MomentsKey(time=0, qubits=QubitSet([Qubit(0)])) Value: Instruction('operator': H, 'target': QubitSet([Qubit(0)])) @@ -157,9 +156,7 @@ def time_slices(self) -> dict[int, list[Instruction]]: return time_slices - def add( - self, instructions: Union[Iterable[Instruction], Instruction], noise_index: int = 0 - ) -> None: + def add(self, instructions: Iterable[Instruction] | Instruction, noise_index: int = 0) -> None: """Add one or more instructions to self. Args: diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index e5d4fdf8a..3ae4153e4 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections.abc import Iterable, Sequence -from typing import Any, ClassVar, Optional, Union +from typing import Any, ClassVar import numpy as np @@ -38,7 +38,7 @@ class Noise(QuantumOperator): the metadata that defines what the noise channel is and what it does. """ - def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): + def __init__(self, qubit_count: int | None, ascii_symbols: Sequence[str]): """Initializes a `Noise` object. Args: @@ -88,7 +88,7 @@ def to_ir( """ if ir_type == IRType.JAQCD: return self._to_jaqcd(target) - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): @@ -99,8 +99,7 @@ def to_ir( return self._to_openqasm( target, serialization_properties or OpenQASMSerializationProperties() ) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self, target: QubitSet) -> Any: """Returns the JAQCD representation of the noise. @@ -176,8 +175,8 @@ class SingleProbabilisticNoise(Noise, Parameterizable): def __init__( self, - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + probability: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], max_probability: float = 0.5, ): @@ -219,7 +218,7 @@ def __str__(self): return f"{self.name}({self.probability})" @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -261,15 +260,15 @@ def to_dict(self) -> dict: } -class SingleProbabilisticNoise_34(SingleProbabilisticNoise): +class SingleProbabilisticNoise_34(SingleProbabilisticNoise): # noqa: N801 """Class `SingleProbabilisticNoise` represents the Depolarizing and TwoQubitDephasing noise channels parameterized by a single probability. """ def __init__( self, - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + probability: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Initializes a `SingleProbabilisticNoise_34`. @@ -295,15 +294,15 @@ def __init__( ) -class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): +class SingleProbabilisticNoise_1516(SingleProbabilisticNoise): # noqa: N801 """Class `SingleProbabilisticNoise` represents the TwoQubitDepolarizing noise channel parameterized by a single probability. """ def __init__( self, - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + probability: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Initializes a `SingleProbabilisticNoise_1516`. @@ -338,8 +337,8 @@ class MultiQubitPauliNoise(Noise, Parameterizable): def __init__( self, - probabilities: dict[str, Union[FreeParameterExpression, float]], - qubit_count: Optional[int], + probabilities: dict[str, FreeParameterExpression | float], + qubit_count: int | None, ascii_symbols: Sequence[str], ): """[summary] @@ -433,7 +432,7 @@ def probabilities(self) -> dict[str, float]: return self._probabilities @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -485,10 +484,10 @@ class PauliNoise(Noise, Parameterizable): def __init__( self, - probX: Union[FreeParameterExpression, float], - probY: Union[FreeParameterExpression, float], - probZ: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + probX: FreeParameterExpression | float, + probY: FreeParameterExpression | float, + probZ: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Initializes a `PauliNoise`. @@ -522,7 +521,7 @@ def __init__( self._parameters = [probX, probY, probZ] @staticmethod - def _get_param_float(param: Union[FreeParameterExpression, float], param_name: str) -> float: + def _get_param_float(param: FreeParameterExpression | float, param_name: str) -> float: """Validates the value of a probability and returns its value. If param is a free parameter expression, this method returns 0. @@ -540,7 +539,7 @@ def _get_param_float(param: Union[FreeParameterExpression, float], param_name: s return float(param) @property - def probX(self) -> Union[FreeParameterExpression, float]: + def probX(self) -> FreeParameterExpression | float: """The probability of a Pauli X error. Returns: @@ -549,7 +548,7 @@ def probX(self) -> Union[FreeParameterExpression, float]: return self._parameters[0] @property - def probY(self) -> Union[FreeParameterExpression, float]: + def probY(self) -> FreeParameterExpression | float: """The probability of a Pauli Y error. Returns: @@ -558,7 +557,7 @@ def probY(self) -> Union[FreeParameterExpression, float]: return self._parameters[1] @property - def probZ(self) -> Union[FreeParameterExpression, float]: + def probZ(self) -> FreeParameterExpression | float: """The probability of a Pauli Z error. Returns: @@ -585,7 +584,7 @@ def __eq__(self, other: PauliNoise): return False @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -633,8 +632,8 @@ class DampingNoise(Noise, Parameterizable): def __init__( self, - gamma: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + gamma: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Initializes a `DampingNoise`. @@ -676,7 +675,7 @@ def __str__(self): return f"{self.name}({self.gamma})" @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -725,9 +724,9 @@ class GeneralizedAmplitudeDampingNoise(DampingNoise): def __init__( self, - gamma: Union[FreeParameterExpression, float], - probability: Union[FreeParameterExpression, float], - qubit_count: Optional[int], + gamma: FreeParameterExpression | float, + probability: FreeParameterExpression | float, + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Inits a `GeneralizedAmplitudeDampingNoise`. @@ -776,7 +775,7 @@ def __str__(self): return f"{self.name}({self.gamma}, {self.probability})" @property - def parameters(self) -> list[Union[FreeParameterExpression, float]]: + def parameters(self) -> list[FreeParameterExpression | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -814,7 +813,7 @@ def to_dict(self) -> dict: def _validate_param_value( - parameter: Union[FreeParameterExpression, float], param_name: str, maximum: float = 1.0 + parameter: FreeParameterExpression | float, param_name: str, maximum: float = 1.0 ) -> None: """Validates the value of a given parameter. @@ -829,7 +828,7 @@ def _validate_param_value( raise ValueError(f"{param_name} must be a real number in the interval [0, {maximum}]") -def _parameter_to_dict(parameter: Union[FreeParameter, float]) -> Union[dict, float]: +def _parameter_to_dict(parameter: FreeParameter | float) -> dict | float: """Converts a parameter to a dictionary if it's a FreeParameter, otherwise returns the float. Args: diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index a73b7f338..708ad0464 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -15,7 +15,7 @@ import warnings from collections.abc import Iterable -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Any import numpy as np @@ -105,7 +105,7 @@ def check_noise_target_unitary(noise: Noise, target_unitary: np.ndarray) -> None def check_noise_target_qubits( - circuit: Circuit, target_qubits: Optional[QubitSetInput] = None + circuit: Circuit, target_qubits: QubitSetInput | None = None ) -> QubitSet: """Helper function to check whether all the target_qubits are positive integers. @@ -155,15 +155,15 @@ def apply_noise_to_moments( for noise_channel in noise: if noise_channel.qubit_count == 1: new = [Instruction(noise_channel, qubit) for qubit in target_qubits] - noise_instructions = noise_instructions + new + noise_instructions += new else: noise_instructions.append(Instruction(noise_channel, target_qubits)) new_moments = Moments() if position == "initialization": - for noise in noise_instructions: - new_moments.add_noise(noise, "initialization_noise") + for noise_instr in noise_instructions: + new_moments.add_noise(noise_instr, "initialization_noise") # add existing instructions for moment_key in circuit.moments: @@ -176,8 +176,8 @@ def apply_noise_to_moments( new_moments.add([instruction], moment_key.noise_index) if position == "readout": - for noise in noise_instructions: - new_moments.add_noise(noise, "readout_noise") + for noise_instr in noise_instructions: + new_moments.add_noise(noise_instr, "readout_noise") circuit._moments = new_moments @@ -226,9 +226,10 @@ def _apply_noise_to_gates_helper( and instruction.target.issubset(target_qubits) ): noise_index += 1 - new_noise_instruction.append( - (Instruction(noise_channel, instruction.target), noise_index) - ) + new_noise_instruction.append(( + Instruction(noise_channel, instruction.target), + noise_index, + )) noise_applied = True return new_noise_instruction, noise_index, noise_applied @@ -237,7 +238,7 @@ def _apply_noise_to_gates_helper( def apply_noise_to_gates( circuit: Circuit, noise: Iterable[type[Noise]], - target_gates: Union[Iterable[type[Gate]], np.ndarray], + target_gates: Iterable[type[Gate]] | np.ndarray, target_qubits: QubitSet, ) -> Circuit: """Apply noise after target gates in target qubits. diff --git a/src/braket/circuits/noise_model/__init__.py b/src/braket/circuits/noise_model/__init__.py index 717e5057f..f1d25637b 100644 --- a/src/braket/circuits/noise_model/__init__.py +++ b/src/braket/circuits/noise_model/__init__.py @@ -11,20 +11,23 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.circuits.noise_model.circuit_instruction_criteria import ( # noqa: F401 - CircuitInstructionCriteria, +from braket.circuits.noise_model.circuit_instruction_criteria import ( + CircuitInstructionCriteria, # noqa: F401 ) -from braket.circuits.noise_model.criteria import ( # noqa: F401 - Criteria, - CriteriaKey, - CriteriaKeyResult, +from braket.circuits.noise_model.criteria import ( + Criteria, # noqa: F401 + CriteriaKey, # noqa: F401 + CriteriaKeyResult, # noqa: F401 ) from braket.circuits.noise_model.gate_criteria import GateCriteria # noqa: F401 from braket.circuits.noise_model.initialization_criteria import InitializationCriteria # noqa: F401 -from braket.circuits.noise_model.noise_model import NoiseModel, NoiseModelInstruction # noqa: F401 +from braket.circuits.noise_model.noise_model import ( + NoiseModel, # noqa: F401 + NoiseModelInstruction, # noqa: F401 +) from braket.circuits.noise_model.observable_criteria import ObservableCriteria # noqa: F401 -from braket.circuits.noise_model.qubit_initialization_criteria import ( # noqa: F401 - QubitInitializationCriteria, +from braket.circuits.noise_model.qubit_initialization_criteria import ( + QubitInitializationCriteria, # noqa: F401 ) from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria # noqa: F401 from braket.circuits.noise_model.unitary_gate_criteria import UnitaryGateCriteria # noqa: F401 diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py index 889211342..d0e7e7a8c 100644 --- a/src/braket/circuits/noise_model/criteria.py +++ b/src/braket/circuits/noise_model/criteria.py @@ -16,7 +16,7 @@ from abc import ABC, abstractmethod from collections.abc import Iterable from enum import Enum -from typing import Any, Union +from typing import Any class CriteriaKey(str, Enum): @@ -54,7 +54,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: raise NotImplementedError @abstractmethod - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Returns a set of key for a given key type. Args: diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index e8a603075..cca792e08 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -15,7 +15,6 @@ from collections import defaultdict from dataclasses import dataclass -from typing import Optional from braket.circuits.circuit import Circuit from braket.circuits.gate import Gate @@ -38,9 +37,9 @@ class NoiseModelInstruction: def __init__(self, noise: Noise, criteria: Criteria): if not isinstance(noise, Noise): - raise ValueError(f"{noise} must be a Noise type.") + raise TypeError(f"{noise} must be a Noise type.") if not isinstance(criteria, Criteria): - raise ValueError(f"{criteria} must be a Criteria type.") + raise TypeError(f"{criteria} must be a Criteria type.") self.noise = noise self.criteria = criteria @@ -89,7 +88,7 @@ class NoiseModel: a phase flip. """ - def __init__(self, instructions: list[NoiseModelInstruction] = None): + def __init__(self, instructions: list[NoiseModelInstruction] | None = None): self._instructions = instructions or [] def __repr__(self): @@ -194,9 +193,9 @@ def get_instructions_by_type(self) -> NoiseModelInstructions: def from_filter( self, - qubit: Optional[QubitSetInput] = None, - gate: Optional[Gate] = None, - noise: Optional[type[Noise]] = None, + qubit: QubitSetInput | None = None, + gate: Gate | None = None, + noise: type[Noise] | None = None, ) -> NoiseModel: """Returns a new NoiseModel from this NoiseModel using a given filter. If no filters are specified, the returned NoiseModel will be the same as this one. diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index a8829f1a4..a167b2a1c 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -1164,7 +1164,7 @@ def generalized_amplitude_damping( Iterable[Instruction]: `Iterable` of GeneralizedAmplitudeDamping instructions. Examples: - >>> circ = Circuit().generalized_amplitude_damping(0, gamma=0.1, probability = 0.9) + >>> circ = Circuit().generalized_amplitude_damping(0, gamma=0.1, probability=0.9) """ return [ Instruction( @@ -1366,7 +1366,7 @@ def _to_openqasm( np.array2string( matrix, separator=", ", - formatter={"all": lambda x: format_complex(x)}, + formatter={"all": format_complex}, ).replace("\n", "") for matrix in self._matrices ) @@ -1402,7 +1402,7 @@ def kraus( Examples: >>> K0 = np.eye(4) * np.sqrt(0.9) - >>> K1 = np.kron([[1., 0.],[0., 1.]], [[0., 1.],[1., 0.]]) * np.sqrt(0.1) + >>> K1 = np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1) >>> circ = Circuit().kraus([1, 0], matrices=[K0, K1]) """ if 2 ** len(targets) != matrices[0].shape[0]: diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index e0572e3d9..7e86ab33e 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -41,11 +41,10 @@ def __init__( ): super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) targets = QubitSet(targets) - if targets: - if (num_targets := len(targets)) != qubit_count: - raise ValueError( - f"Length of target {num_targets} does not match qubit count {qubit_count}" - ) + if targets and (num_targets := len(targets)) != qubit_count: + raise ValueError( + f"Length of target {num_targets} does not match qubit count {qubit_count}" + ) self._targets = targets self._coef = 1 @@ -80,7 +79,7 @@ def to_ir( """ if ir_type == IRType.JAQCD: return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): @@ -91,8 +90,7 @@ def to_ir( return self._to_openqasm( serialization_properties or OpenQASMSerializationProperties(), target ) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self) -> list[str | list[list[list[float]]]]: """Returns the JAQCD representation of the observable.""" @@ -177,7 +175,7 @@ def __matmul__(self, other: Observable) -> Observable.TensorProduct: if isinstance(other, Observable): return Observable.TensorProduct([self, other]) - raise ValueError("Can only perform tensor products between observables.") + raise TypeError("Can only perform tensor products between observables.") def __mul__(self, other: Observable) -> Observable: """Scalar multiplication""" @@ -192,13 +190,13 @@ def __rmul__(self, other: Observable) -> Observable: def __add__(self, other: Observable): if not isinstance(other, Observable): - raise ValueError("Can only perform addition between observables.") + raise TypeError("Can only perform addition between observables.") return Observable.Sum([self, other]) def __sub__(self, other: Observable): if not isinstance(other, Observable): - raise ValueError("Can only perform subtraction between observables.") + raise TypeError("Can only perform subtraction between observables.") return self + (-1 * other) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 8249d156f..5be86554a 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -18,7 +18,7 @@ import math import numbers from copy import deepcopy -from typing import ClassVar, Union +from typing import ClassVar import numpy as np @@ -110,6 +110,7 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" + targets = target or self._targets qubit_target = int(targets[0]) if targets else None if qubit_target is not None: @@ -168,6 +169,7 @@ def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, target: QubitSet = None ) -> str: coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" + targets = target or self._targets qubit_target = int(targets[0]) if targets else None if qubit_target is not None: @@ -532,7 +534,7 @@ def _to_jaqcd(self) -> list[str]: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - target: list[QubitSetInput] = None, + target: list[QubitSetInput] | None = None, ) -> str: target = target or self._targets if len(self.summands) != len(target): @@ -615,8 +617,8 @@ def __init__( or, if targets is supplied, doesn't match the size of targets. Examples: - >>> observables.Hermitian(matrix=np.array([[0, 1],[1, 0]]), targets=[0]) - >>> observables.Hermitian(matrix=np.array([[0, 1],[1, 0]])) + >>> observables.Hermitian(matrix=np.array([[0, 1], [1, 0]]), targets=[0]) + >>> observables.Hermitian(matrix=np.array([[0, 1], [1, 0]])) """ verify_quantum_operator_matrix_dimensions(matrix) self._matrix = np.array(matrix, dtype=complex) @@ -652,15 +654,14 @@ def _to_openqasm( coef_prefix = f"{self.coefficient} * " if self.coefficient != 1 else "" target = target or self._targets if target: - qubit_target = ", ".join( - [serialization_properties.format_target(int(t)) for t in target] - ) + qubit_target = ", ".join([ + serialization_properties.format_target(int(t)) for t in target + ]) return ( f"{coef_prefix}" f"hermitian({self._serialized_matrix_openqasm_matrix()}) {qubit_target}" ) - else: - return f"{coef_prefix}hermitian({self._serialized_matrix_openqasm_matrix()}) all" + return f"{coef_prefix}hermitian({self._serialized_matrix_openqasm_matrix()}) all" def _serialized_matrix_openqasm_matrix(self) -> str: serialized = str([[f"{complex(elem)}" for elem in row] for row in self._matrix.tolist()]) @@ -725,7 +726,7 @@ def __repr__(self): Observable.register_observable(Hermitian) -def observable_from_ir(ir_observable: list[Union[str, list[list[list[float]]]]]) -> Observable: +def observable_from_ir(ir_observable: list[str | list[list[list[float]]]]) -> Observable: """Create an observable from the IR observable list. This can be a tensor product of observables or a single observable. @@ -737,26 +738,24 @@ def observable_from_ir(ir_observable: list[Union[str, list[list[list[float]]]]]) """ if len(ir_observable) == 1: return _observable_from_ir_list_item(ir_observable[0]) - observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) - return observable + return TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) -def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]]]) -> Observable: +def _observable_from_ir_list_item(observable: str | list[list[list[float]]]) -> Observable: if observable == "i": return I() - elif observable == "h": + if observable == "h": return H() - elif observable == "x": + if observable == "x": return X() - elif observable == "y": + if observable == "y": return Y() - elif observable == "z": + if observable == "z": return Z() - else: - try: - matrix = np.array( - [[complex(element[0], element[1]) for element in row] for row in observable] - ) - return Hermitian(matrix) - except Exception as e: - raise ValueError(f"Invalid observable specified: {observable} error: {e}") from e + try: + matrix = np.array([ + [complex(element[0], element[1]) for element in row] for row in observable + ]) + return Hermitian(matrix) + except Exception as e: + raise ValueError(f"Invalid observable specified: {observable} error: {e}") from e diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index b706e1822..d33c69a1c 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections.abc import Sequence -from typing import Any, Optional +from typing import Any import numpy as np @@ -24,7 +24,7 @@ class QuantumOperator(Operator): """A quantum operator is the definition of a quantum operation for a quantum device.""" - def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): + def __init__(self, qubit_count: int | None, ascii_symbols: Sequence[str]): """Initializes a `QuantumOperator`. Args: @@ -55,7 +55,7 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): elif qubit_count and qubit_count != fixed_qubit_count: raise ValueError( f"Provided qubit count {qubit_count}" - "does not equal fixed qubit count {fixed_qubit_count}" + f"does not equal fixed qubit count {fixed_qubit_count}" ) else: self._qubit_count = fixed_qubit_count diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 10c22808e..07c2d09e4 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -118,8 +118,9 @@ def get_pauli_eigenvalues(num_qubits: int) -> np.ndarray: eigs = np.array([1, -1]) eigs.setflags(write=False) return eigs - eigs = np.concatenate( - [get_pauli_eigenvalues(num_qubits - 1), -get_pauli_eigenvalues(num_qubits - 1)] - ) + eigs = np.concatenate([ + get_pauli_eigenvalues(num_qubits - 1), + -get_pauli_eigenvalues(num_qubits - 1), + ]) eigs.setflags(write=False) return eigs diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index 8343429e0..eadd4c02d 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, Union +from typing import Any from braket.circuits.free_parameter import FreeParameter from braket.circuits.observable import Observable @@ -86,7 +86,7 @@ def to_ir( """ if ir_type == IRType.JAQCD: return self._to_jaqcd() - elif ir_type == IRType.OPENQASM: + if ir_type == IRType.OPENQASM: if serialization_properties and not isinstance( serialization_properties, OpenQASMSerializationProperties ): @@ -95,8 +95,7 @@ def to_ir( "for IRType.OPENQASM." ) return self._to_openqasm(serialization_properties or OpenQASMSerializationProperties()) - else: - raise ValueError(f"Supplied ir_type {ir_type} is not supported.") + raise ValueError(f"Supplied ir_type {ir_type} is not supported.") def _to_jaqcd(self) -> Any: """Returns the JAQCD representation of the result type.""" @@ -152,10 +151,10 @@ def copy( >>> new_result_type.target QubitSet(Qubit(5)) """ - copy = self.__copy__() + copy = self.__copy__() # noqa: PLC2801 if target_mapping and target is not None: raise TypeError("Only 'target_mapping' or 'target' can be supplied, but not both.") - elif target is not None: + if target is not None: if hasattr(copy, "target"): copy.target = target elif hasattr(copy, "target"): @@ -288,7 +287,7 @@ def __init__( ascii_symbols: list[str], observable: Observable, target: QubitSetInput | None = None, - parameters: list[Union[str, FreeParameter]] | None = None, + parameters: list[str | FreeParameter] | None = None, ): super().__init__(ascii_symbols, observable, target) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 6c1232b06..9cd42e5b9 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -14,7 +14,6 @@ from __future__ import annotations import re -from typing import Union import braket.ir.jaqcd as ir from braket.circuits import circuit @@ -117,8 +116,7 @@ def _to_jaqcd(self) -> ir.DensityMatrix: if self.target: # convert qubits to int as required by the ir type return ir.DensityMatrix.construct(targets=[int(qubit) for qubit in self.target]) - else: - return ir.DensityMatrix.construct() + return ir.DensityMatrix.construct() def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: if not self.target: @@ -175,7 +173,7 @@ def __init__( self, observable: Observable, target: list[QubitSetInput] | None = None, - parameters: list[Union[str, FreeParameter]] | None = None, + parameters: list[str | FreeParameter] | None = None, ): """Inits an `AdjointGradient`. @@ -235,7 +233,7 @@ def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties def adjoint_gradient( observable: Observable, target: list[QubitSetInput] | None = None, - parameters: list[Union[str, FreeParameter]] | None = None, + parameters: list[str | FreeParameter] | None = None, ) -> ResultType: """Registers this function into the circuit class. @@ -254,7 +252,7 @@ def adjoint_gradient( ResultType: gradient computed via adjoint differentiation as a requested result type Examples: - >>> alpha, beta = FreeParameter('alpha'), FreeParameter('beta') + >>> alpha, beta = FreeParameter("alpha"), FreeParameter("beta") >>> circ = Circuit().h(0).h(1).rx(0, alpha).yy(0, 1, beta).adjoint_gradient( >>> observable=observables.Z(0), parameters=[alpha, beta] >>> ) @@ -283,13 +281,13 @@ def __init__(self, state: list[str]): state is not a list of strings of '0' and '1' Examples: - >>> result_types.Amplitude(state=['01', '10']) + >>> result_types.Amplitude(state=["01", "10"]) """ if ( not state or not isinstance(state, list) or not all( - isinstance(amplitude, str) and re.fullmatch("^[01]+$", amplitude) + isinstance(amplitude, str) and re.fullmatch(r"^[01]+$", amplitude) for amplitude in state ) ): @@ -385,8 +383,7 @@ def _to_jaqcd(self) -> ir.Probability: if self.target: # convert qubits to int as required by the ir type return ir.Probability.construct(targets=[int(qubit) for qubit in self.target]) - else: - return ir.Probability.construct() + return ir.Probability.construct() def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: if not self.target: @@ -472,8 +469,7 @@ def _to_jaqcd(self) -> ir.Expectation: return ir.Expectation.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) - else: - return ir.Expectation.construct(observable=self.observable.to_ir()) + return ir.Expectation.construct(observable=self.observable.to_ir()) def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( @@ -547,8 +543,7 @@ def _to_jaqcd(self) -> ir.Sample: return ir.Sample.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) - else: - return ir.Sample.construct(observable=self.observable.to_ir()) + return ir.Sample.construct(observable=self.observable.to_ir()) def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( @@ -627,8 +622,7 @@ def _to_jaqcd(self) -> ir.Variance: return ir.Variance.construct( observable=self.observable.to_ir(), targets=[int(qubit) for qubit in self.target] ) - else: - return ir.Variance.construct(observable=self.observable.to_ir()) + return ir.Variance.construct(observable=self.observable.to_ir()) def _to_openqasm(self, serialization_properties: OpenQASMSerializationProperties) -> str: observable_ir = self.observable.to_ir( diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index 4a7c9565c..57b591173 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -14,7 +14,7 @@ from __future__ import annotations from functools import reduce -from typing import Literal, Union +from typing import Literal import braket.circuits.circuit as cir from braket.circuits.compiler_directive import CompilerDirective @@ -74,7 +74,7 @@ def _duplicate_time_at_bottom(cls, lines: str) -> None: def _create_diagram_column( cls, circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], + items: list[Instruction | ResultType], global_phase: float | None = None, ) -> str: """Return a column in the ASCII string diagram of the circuit for a given list of items. @@ -88,7 +88,7 @@ def _create_diagram_column( str: an ASCII string diagram for the specified moment in time for a column. """ symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} - connections = {qubit: "none" for qubit in circuit_qubits} + connections = dict.fromkeys(circuit_qubits, "none") for item in items: if isinstance(item, ResultType) and not item.target: @@ -135,7 +135,7 @@ def _create_diagram_column( # Determine if the qubit is part of the item or in the middle of a # multi qubit item. if qubit in target_qubits: - item_qubit_index = [ + item_qubit_index = [ # noqa: RUF015 index for index, q in enumerate(target_qubits) if q == qubit ][0] power_string = ( @@ -164,8 +164,7 @@ def _create_diagram_column( if target_and_control and qubit != min(target_and_control): connections[qubit] = "above" - output = cls._create_output(symbols, connections, circuit_qubits, global_phase) - return output + return cls._create_output(symbols, connections, circuit_qubits, global_phase) # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] # flake8: noqa: BCS005 @@ -185,8 +184,8 @@ def _draw_symbol( Returns: str: a string representing the symbol. """ - connection_char = cls._vertical_delimiter() if connection in ["above"] else " " - output = "{0:{width}}\n".format( + connection_char = cls._vertical_delimiter() if connection == "above" else " " + return "{0:{width}}\n".format( connection_char, width=symbols_width + 1 ) + "{0:{fill}{align}{width}}\n".format( symbol, @@ -194,4 +193,3 @@ def _draw_symbol( align="<", width=symbols_width + 1, ) - return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index ad30b34a7..08bde78c5 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -1,7 +1,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Literal, Union +from typing import Literal import braket.circuits.circuit as cir from braket.circuits.circuit_diagram import CircuitDiagram @@ -178,7 +178,7 @@ def _create_diagram_column_set( cls, col_title: str, circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], + items: list[Instruction | ResultType], global_phase: float | None, ) -> str: """Return a set of columns in the string diagram of the circuit for a list of items. diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index f261b00b6..51b41ceb7 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -14,7 +14,6 @@ from __future__ import annotations from functools import reduce -from typing import Union import braket.circuits.circuit as cir from braket.circuits.compiler_directive import CompilerDirective @@ -43,8 +42,7 @@ def _add_footers( # A list of parameters in the circuit to the currently assigned values. if circuit.parameters: lines.append( - "\nUnassigned parameters: " - f"{sorted(circuit.parameters, key=lambda param: param.name)}." + f"\nUnassigned parameters: {sorted(circuit.parameters, key=lambda param: param.name)}." ) return "\n".join(lines) @@ -117,7 +115,7 @@ def _compute_moment_global_phase( def _group_items( circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], + items: list[Instruction | ResultType], ) -> list[tuple[QubitSet, list[Instruction]]]: """ Group instructions in a moment diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 85567de28..df8ffc9ed 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -90,7 +90,7 @@ def _create_diagram_column( str: a string diagram for the specified moment in time for a column. """ symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} - connections = {qubit: "none" for qubit in circuit_qubits} + connections = dict.fromkeys(circuit_qubits, "none") for item in items: ( @@ -106,7 +106,7 @@ def _create_diagram_column( # Determine if the qubit is part of the item or in the middle of a # multi qubit item. if qubit in target_qubits: - item_qubit_index = [ + item_qubit_index = [ # noqa: RUF015 index for index, q in enumerate(target_qubits) if q == qubit ][0] power_string = ( @@ -132,8 +132,7 @@ def _create_diagram_column( else: symbols[qubit] = "┼" - output = cls._create_output(symbols, connections, circuit_qubits, global_phase) - return output + return cls._create_output(symbols, connections, circuit_qubits, global_phase) @classmethod def _build_parameters( @@ -184,7 +183,7 @@ def _build_parameters( @staticmethod def _update_connections(qubits: QubitSet, connections: dict[Qubit, str]) -> None: if len(qubits) > 1: - connections |= {qubit: "both" for qubit in qubits[1:-1]} + connections |= dict.fromkeys(qubits[1:-1], "both") connections[qubits[-1]] = "above" connections[qubits[0]] = "below" @@ -212,15 +211,15 @@ def _draw_symbol( top = "" bottom = "" if symbol in {"C", "N", "SWAP"}: - if connection in ["above", "both"]: + if connection in {"above", "both"}: top = _fill_symbol(cls._vertical_delimiter(), " ") - if connection in ["below", "both"]: + if connection in {"below", "both"}: bottom = _fill_symbol(cls._vertical_delimiter(), " ") new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} # replace SWAP by x # the size of the moment remains as if there was a box with 4 characters inside symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) - elif symbol in ["StartVerbatim", "EndVerbatim"]: + elif symbol in {"StartVerbatim", "EndVerbatim"}: top, symbol, bottom = cls._build_verbatim_box(symbol, connection) elif symbol == "┼": top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") @@ -229,7 +228,10 @@ def _draw_symbol( top, symbol, bottom = cls._build_box(symbol, connection) output = f"{_fill_symbol(top, ' ', symbols_width)} \n" - output += f"{_fill_symbol(symbol, cls._qubit_line_character(), symbols_width)}{cls._qubit_line_character()}\n" + output += ( + f"{_fill_symbol(symbol, cls._qubit_line_character(), symbols_width)}" + f"{cls._qubit_line_character()}\n" + ) output += f"{_fill_symbol(bottom, ' ', symbols_width)} \n" return output @@ -237,10 +239,10 @@ def _draw_symbol( def _build_box( symbol: str, connection: Literal["above", "below", "both", "none"] ) -> tuple[str, str, str]: - top_edge_symbol = "┴" if connection in ["above", "both"] else "─" + top_edge_symbol = "┴" if connection in {"above", "both"} else "─" top = f"┌─{_fill_symbol(top_edge_symbol, '─', len(symbol))}─┐" - bottom_edge_symbol = "┬" if connection in ["below", "both"] else "─" + bottom_edge_symbol = "┬" if connection in {"below", "both"} else "─" bottom = f"└─{_fill_symbol(bottom_edge_symbol, '─', len(symbol))}─┘" symbol = f"┤ {symbol} ├" diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 78bb7eed0..e67e3483f 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -11,8 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + +import operator from functools import reduce, singledispatch -from typing import Union +from typing import NoReturn import braket.circuits.gates as braket_gates import braket.circuits.result_types as ResultTypes # noqa: N812 @@ -98,11 +101,11 @@ } -def get_observable(obs: Union[models.Observable, list]) -> Observable: +def get_observable(obs: models.Observable | list) -> Observable: """Gets the observable. Args: - obs (Union[Observable, list]): The observable(s) to get translated. + obs (models.Observable | list): The observable(s) to get translated. Returns: Observable: The translated observable. @@ -111,12 +114,12 @@ def get_observable(obs: Union[models.Observable, list]) -> Observable: @singledispatch -def _get_observable(obs: Union[models.Observable, list]) -> Observable: +def _get_observable(obs: models.Observable | list) -> Observable: raise NotImplementedError @_get_observable.register(list) -def _(obs): +def _(obs: Observable) -> NoReturn: raise NotImplementedError @@ -125,17 +128,17 @@ def _(name: str): return getattr(observables, name.upper())() -def get_tensor_product(observable: Union[models.Observable, list]) -> Observable: +def get_tensor_product(observable: models.Observable | list) -> Observable: """Generate an braket circuit observable Args: - observable (Union[Observable, list]): ir observable or a matrix + observable (Observable | list): ir observable or a matrix Returns: Observable: braket circuit observable """ circuit_observable = [get_observable(obs) for obs in observable] - return reduce(lambda obs1, obs2: obs1 @ obs2, circuit_observable) + return reduce(operator.matmul, circuit_observable) @singledispatch diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index af3467a40..da3e37fab 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -126,7 +126,7 @@ def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: if not noise_operators <= supported_noises: raise ValueError( f"{self.name} does not support noise simulation or the noise model includes noise " - + f"that is not supported by {self.name}." + f"that is not supported by {self.name}." ) def _apply_noise_model_to_circuit( @@ -137,7 +137,8 @@ def _apply_noise_model_to_circuit( if isinstance(instruction.operator, Noise): warnings.warn( "The noise model of the device is applied to a circuit that already has" - " noise instructions." + " noise instructions.", + stacklevel=2, ) break task_specification = self._noise_model.apply(task_specification) @@ -145,6 +146,7 @@ def _apply_noise_model_to_circuit( warnings.warn( "Noise model is only applicable to circuits. The type of the task specification is" f" {task_specification.__class__.__name__}. The noise model of the device does not" - " apply." + " apply.", + stacklevel=2, ) return task_specification diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index f9e70a1ea..4371f8bc0 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -17,7 +17,7 @@ from functools import singledispatchmethod from itertools import repeat from os import cpu_count -from typing import Any, Optional, Union +from typing import Any, Optional from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem @@ -59,7 +59,7 @@ class LocalSimulator(Device): def __init__( self, - backend: Union[str, BraketSimulator] = "default", + backend: str | BraketSimulator = "default", noise_model: Optional[NoiseModel] = None, ): """Initializes a `LocalSimulator`. @@ -84,9 +84,11 @@ def __init__( def run( self, - task_specification: Union[ - Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram - ], + task_specification: Circuit + | Problem + | OpenQASMProgram + | AnalogHamiltonianSimulation + | SerializableProgram, shots: int = 0, inputs: Optional[dict[str, float]] = None, *args: Any, @@ -95,7 +97,7 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]): # noqa E501 + task_specification (Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]): The quantum task specification. shots (int): The number of times to run the circuit or annealing problem. Default is 0, which means that the simulator will compute the exact @@ -119,32 +121,26 @@ def run( >>> circuit = Circuit().h(0).cnot(0, 1) >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) - """ + """ # noqa: E501 if self._noise_model: task_specification = self._apply_noise_model_to_circuit(task_specification) payload = self._construct_payload(task_specification, inputs, shots) result = self._delegate.run(payload, *args, shots=shots, **kwargs) return LocalQuantumTask(self._to_result_object(result)) - def run_batch( # noqa: C901 + def run_batch( self, - task_specifications: Union[ - Union[ - Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram - ], - list[ - Union[ - Circuit, - Problem, - OpenQASMProgram, - AnalogHamiltonianSimulation, - SerializableProgram, - ] - ], + task_specifications: Circuit + | Problem + | OpenQASMProgram + | AnalogHamiltonianSimulation + | SerializableProgram + | list[ + Circuit | Problem | OpenQASMProgram | AnalogHamiltonianSimulation | SerializableProgram ], - shots: Optional[int] = 0, - max_parallel: Optional[int] = None, - inputs: Optional[Union[dict[str, float], list[dict[str, float]]]] = None, + shots: int | None = 0, + max_parallel: int | None = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, *args, **kwargs, ) -> LocalQuantumTaskBatch: @@ -166,7 +162,7 @@ def run_batch( # noqa: C901 See Also: `braket.tasks.local_quantum_task_batch.LocalQuantumTaskBatch` - """ # noqa E501 + """ # noqa: E501 inputs = inputs or {} if self._noise_model: @@ -206,8 +202,7 @@ def run_batch( # noqa: C901 param_names = {param.name for param in task_specification.parameters} if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( - f"Cannot execute circuit with unbound parameters: " - f"{unbounded_parameters}" + f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" ) payloads.append(self._construct_payload(task_specification, input_map, shots)) @@ -241,7 +236,7 @@ def _get_simulator(self, simulator: Any) -> BraketSimulator: raise TypeError("Simulator must either be a string or a BraketSimulator instance") @_get_simulator.register - def _(self, backend_name: str): + def _(self, backend_name: str) -> BraketSimulator: if backend_name not in _simulator_devices: raise ValueError( f"Only the following devices are available {_simulator_devices.keys()}" @@ -250,15 +245,15 @@ def _(self, backend_name: str): return device_class() @_get_simulator.register - def _(self, backend_impl: BraketSimulator): + def _(self, backend_impl: BraketSimulator) -> BraketSimulator: return backend_impl @singledispatchmethod def _construct_payload( self, task_specification: Any, - inputs: Optional[dict[str, float]], - shots: Optional[int], + inputs: dict[str, float] | None, + shots: int | None, ) -> Any: raise NotImplementedError(f"Unsupported task type {type(task_specification)}") @@ -270,13 +265,13 @@ def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: Optiona program = circuit.to_ir(ir_type=IRType.OPENQASM) program.inputs.update(inputs or {}) return program - elif DeviceActionType.JAQCD in simulator.properties.action: + if DeviceActionType.JAQCD in simulator.properties.action: validate_circuit_and_shots(circuit, shots) return circuit.to_ir(ir_type=IRType.JAQCD) raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") @_construct_payload.register - def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots): + def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots: int): simulator = self._delegate if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") @@ -290,12 +285,12 @@ def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots return program @_construct_payload.register - def _(self, program: SerializableProgram, inputs: Optional[dict[str, float]], _shots): + def _(self, program: SerializableProgram, inputs: Optional[dict[str, float]], _shots: int): inputs_copy = inputs.copy() if inputs is not None else {} return OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM), inputs=inputs_copy) @_construct_payload.register - def _(self, program: AnalogHamiltonianSimulation, _inputs, _shots): + def _(self, program: AnalogHamiltonianSimulation, _inputs: dict[str, float], _shots: int): simulator = self._delegate if DeviceActionType.AHS not in simulator.properties.action: raise NotImplementedError( @@ -304,7 +299,7 @@ def _(self, program: AnalogHamiltonianSimulation, _inputs, _shots): return program.to_ir() @_construct_payload.register - def _(self, program: AHSProgram, _inputs, _shots): + def _(self, program: AHSProgram, _inputs: dict[str, float], _shots: int): simulator = self._delegate if DeviceActionType.AHS not in simulator.properties.action: raise NotImplementedError( @@ -313,7 +308,7 @@ def _(self, program: AHSProgram, _inputs, _shots): return program @_construct_payload.register - def _(self, problem: Problem, _inputs, _shots): + def _(self, problem: Problem, _inputs: dict[str, float], _shots: int): simulator = self._delegate if DeviceActionType.ANNEALING not in simulator.properties.action: raise NotImplementedError( @@ -326,17 +321,19 @@ def _to_result_object(self, result: Any) -> Any: raise NotImplementedError(f"Unsupported task result type {type(result)}") @_to_result_object.register - def _(self, result: GateModelQuantumTaskResult): + def _(self, result: GateModelQuantumTaskResult) -> GateModelQuantumTaskResult: return result @_to_result_object.register - def _(self, result: GateModelTaskResult): + def _(self, result: GateModelTaskResult) -> GateModelQuantumTaskResult: return GateModelQuantumTaskResult.from_object(result) @_to_result_object.register - def _(self, result: AnalogHamiltonianSimulationTaskResult): + def _( + self, result: AnalogHamiltonianSimulationTaskResult + ) -> AnalogHamiltonianSimulationQuantumTaskResult: return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) @_to_result_object.register - def _(self, result: AnnealingTaskResult): + def _(self, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: return AnnealingQuantumTaskResult.from_object(result) diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index 0386ed7a7..607901a3a 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -58,7 +58,7 @@ def save_job_checkpoint( if checkpoint_file_suffix else f"{checkpoint_directory}/{job_name}.json" ) - with open(checkpoint_file_path, "w") as f: + with open(checkpoint_file_path, "w", encoding="utf-8") as f: serialized_data = serialize_values(checkpoint_data or {}, data_format) persisted_data = PersistedJobData(dataDictionary=serialized_data, dataFormat=data_format) f.write(persisted_data.json()) @@ -102,18 +102,15 @@ def load_job_checkpoint( if checkpoint_file_suffix else f"{checkpoint_directory}/{job_name}.json" ) - with open(checkpoint_file_path) as f: + with open(checkpoint_file_path, encoding="utf-8") as f: persisted_data = PersistedJobData.parse_raw(f.read()) - deserialized_data = deserialize_values( - persisted_data.dataDictionary, persisted_data.dataFormat - ) - return deserialized_data + return deserialize_values(persisted_data.dataDictionary, persisted_data.dataFormat) def _load_persisted_data(filename: str | Path | None = None) -> PersistedJobData: filename = filename or Path(get_results_dir()) / "results.json" try: - with open(filename) as f: + with open(filename, encoding="utf-8") as f: return PersistedJobData.parse_raw(f.read()) except FileNotFoundError: return PersistedJobData( @@ -134,8 +131,7 @@ def load_job_result(filename: str | Path | None = None) -> dict[str, Any]: dict[str, Any]: Job result data of current job """ persisted_data = _load_persisted_data(filename) - deserialized_data = deserialize_values(persisted_data.dataDictionary, persisted_data.dataFormat) - return deserialized_data + return deserialize_values(persisted_data.dataDictionary, persisted_data.dataFormat) def save_job_result( @@ -186,7 +182,7 @@ def save_job_result( ) updated_results = current_results | result_data - with open(Path(get_results_dir()) / "results.json", "w") as f: + with open(Path(get_results_dir()) / "results.json", "w", encoding="utf-8") as f: serialized_data = serialize_values(updated_results or {}, data_format) persisted_data = PersistedJobData(dataDictionary=serialized_data, dataFormat=data_format) f.write(persisted_data.json()) diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py index 6d7d18364..8d3fc3f97 100644 --- a/src/braket/jobs/environment_variables.py +++ b/src/braket/jobs/environment_variables.py @@ -72,6 +72,6 @@ def get_hyperparameters() -> dict[str, str]: dict[str, str]: The hyperparameters of the job. """ if "AMZN_BRAKET_HP_FILE" in os.environ: - with open(os.getenv("AMZN_BRAKET_HP_FILE")) as f: + with open(os.getenv("AMZN_BRAKET_HP_FILE"), encoding="utf-8") as f: return json.load(f) return {} diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index 0f80e7fd9..cda249535 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -196,20 +196,19 @@ def job_wrapper(*args: Any, **kwargs: Any) -> Callable: tempfile.TemporaryDirectory(dir="", prefix="decorator_job_") as temp_dir, persist_inner_function_source(entry_point) as inner_source_input, ): - job_input_data = _add_inner_function_source_to_input_data( input_data, inner_source_input ) temp_dir_path = Path(temp_dir) entry_point_file_path = Path("entry_point.py") - with open(temp_dir_path / entry_point_file_path, "w") as entry_point_file: - template = "\n".join( - [ - _process_input_data(input_data), - _serialize_entry_point(entry_point, args, kwargs), - ] - ) + with open( + temp_dir_path / entry_point_file_path, "w", encoding="utf-8" + ) as entry_point_file: + template = "\n".join([ + _process_input_data(input_data), + _serialize_entry_point(entry_point, args, kwargs), + ]) entry_point_file.write(template) if dependencies: @@ -241,12 +240,8 @@ def job_wrapper(*args: Any, **kwargs: Any) -> Callable: "quiet": quiet, "reservation_arn": reservation_arn, } - for key, value in optional_args.items(): - if value is not None: - job_args[key] = value - - job = _create_job(job_args, local) - return job + job_args.update({key: val for key, val in optional_args.items() if val is not None}) + return _create_job(job_args, local) return job_wrapper @@ -259,6 +254,10 @@ def persist_inner_function_source(entry_point: callable) -> None: and replace the source file path with the saved one. Args: entry_point (callable): The job decorated function. + + Yields: + dict: if the inner function exists, a mapping of the input channel to the copy directory. + Otherwise an empty dict """ inner_source_mapping = _get_inner_function_source(entry_point.__code__) @@ -289,14 +288,14 @@ def _replace_inner_function_source_path( """ new_co_consts = [] for const in code_object.co_consts: + new_const = const if inspect.iscode(const): new_path = path_mapping[const.co_filename] - const = const.replace(co_filename=new_path) - const = _replace_inner_function_source_path(const, path_mapping) - new_co_consts.append(const) + new_const = const.replace(co_filename=new_path) + new_const = _replace_inner_function_source_path(new_const, path_mapping) + new_co_consts.append(new_const) - code_object = code_object.replace(co_consts=tuple(new_co_consts)) - return code_object + return code_object.replace(co_consts=tuple(new_co_consts)) def _save_inner_source_to_file(inner_source: dict[str, str], input_data_dir: str) -> dict[str, str]: @@ -311,7 +310,7 @@ def _save_inner_source_to_file(inner_source: dict[str, str], input_data_dir: str path_mapping = {} for i, (local_path, source_code) in enumerate(inner_source.items()): copy_file_name = f"source_{i}.py" - with open(f"{input_data_dir}/{copy_file_name}", "w") as f: + with open(f"{input_data_dir}/{copy_file_name}", "w", encoding="utf-8") as f: f.write(source_code) path_mapping[local_path] = os.path.join( @@ -392,12 +391,12 @@ def _process_dependencies(dependencies: str | Path | list[str], temp_dir: Path) shutil.copy(Path(dependencies).resolve(), temp_dir / "requirements.txt") else: # list of packages - with open(temp_dir / "requirements.txt", "w") as f: + with open(temp_dir / "requirements.txt", "w", encoding="utf-8") as f: f.write("\n".join(dependencies)) class _IncludeModules: - def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] = None): + def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] | None = None): modules = modules or [] if isinstance(modules, (str, ModuleType)): modules = [modules] @@ -411,7 +410,7 @@ def __enter__(self): for module in self._modules: cloudpickle.register_pickle_by_value(module) - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): # noqa: ANN001 """Unregister included modules with cloudpickle to be pickled by value""" for module in self._modules: cloudpickle.unregister_pickle_by_value(module) @@ -445,10 +444,10 @@ def _log_hyperparameters(entry_point: Callable, args: tuple, kwargs: dict) -> di hyperparameters = {} for param, value in bound_args.arguments.items(): param_kind = signature.parameters[param].kind - if param_kind in [ + if param_kind in { inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.KEYWORD_ONLY, - ]: + }: hyperparameters[param] = value elif param_kind == inspect.Parameter.VAR_KEYWORD: hyperparameters.update(**value) @@ -479,7 +478,7 @@ def _sanitize(hyperparameter: Any) -> str: # max allowed length for a hyperparameter is 2500 if len(sanitized) > 2500: # show as much as possible, including the final 20 characters - return f"{sanitized[:2500 - 23]}...{sanitized[-20:]}" + return f"{sanitized[: 2500 - 23]}...{sanitized[-20:]}" return sanitized @@ -536,7 +535,7 @@ def is_prefix(path: str) -> bool: def _create_job(job_args: dict[str, Any], local: bool = False) -> QuantumJob: """Create an AWS or Local hybrid job""" if local: - from braket.jobs.local import LocalQuantumJob + from braket.jobs.local import LocalQuantumJob # noqa: PLC0415 for aws_only_arg in [ "wait_until_complete", @@ -547,10 +546,8 @@ def _create_job(job_args: dict[str, Any], local: bool = False) -> QuantumJob: "tags", "logger", ]: - if aws_only_arg in job_args: - del job_args[aws_only_arg] + job_args.pop(aws_only_arg, None) return LocalQuantumJob.create(**job_args) - else: - from braket.aws import AwsQuantumJob + from braket.aws import AwsQuantumJob # noqa: PLC0415 - return AwsQuantumJob.create(**job_args) + return AwsQuantumJob.create(**job_args) diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index af6c5012a..213d06553 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -70,7 +70,7 @@ def _config_for_framework(framework: Framework) -> dict[str, str]: dict[str, str]: Dict that contains the configuration for the specified framework. """ fname = os.path.join(os.path.dirname(__file__), "image_uri_config", f"{framework.lower()}.json") - with open(fname) as f: + with open(fname, encoding="utf-8") as f: return json.load(f) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index 4dd15607a..cac12a097 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -158,7 +158,7 @@ def create( env_variables = setup_container(container, session, **create_job_kwargs) container.run_local_job(env_variables) container.copy_from("/opt/ml/model", job_name) - with open(os.path.join(job_name, "log.txt"), "w") as log_file: + with open(os.path.join(job_name, "log.txt"), "w", encoding="utf-8") as log_file: log_file.write(container.run_log) if "checkpointConfig" in create_job_kwargs: checkpoint_config = create_job_kwargs["checkpointConfig"] @@ -209,7 +209,7 @@ def run_log(self) -> str: """ if not self._run_log: try: - with open(os.path.join(self.name, "log.txt")) as log_file: + with open(os.path.join(self.name, "log.txt"), encoding="utf-8") as log_file: self._run_log = log_file.read() except FileNotFoundError as e: raise ValueError( @@ -289,12 +289,9 @@ def result( dict[str, Any]: Dict specifying the hybrid job results. """ try: - with open(os.path.join(self.name, "results.json")) as f: + with open(os.path.join(self.name, "results.json"), encoding="utf-8") as f: persisted_data = PersistedJobData.parse_raw(f.read()) - deserialized_data = deserialize_values( - persisted_data.dataDictionary, persisted_data.dataFormat - ) - return deserialized_data + return deserialize_values(persisted_data.dataDictionary, persisted_data.dataFormat) except FileNotFoundError as e: raise ValueError( f"Unable to find results in the local job directory {self.name}." diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 6d9d08f4f..13eb90e43 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -60,7 +60,7 @@ def __enter__(self): self._container_name = self._start_container(self.image_uri, self._force_update) return self - def __exit__(self, exc_type, exc_val, exc_tb): + def __exit__(self, exc_type, exc_val, exc_tb): # noqa: ANN001 """Stops and removes the local docker container.""" self._end_session() @@ -79,9 +79,8 @@ def _envs_to_list(environment_variables: dict[str, str]) -> list[str]: provided environment variables as part of the runtime. """ env_list = [] - for key in environment_variables: - env_list.append("-e") - env_list.append(f"{key}={environment_variables[key]}") + for key, val in environment_variables.items(): + env_list.extend(("-e", f"{key}={val}")) return env_list @staticmethod @@ -174,9 +173,16 @@ def _start_container(self, image_uri: str, force_update: bool) -> str: except ValueError: self._logger.warning(f"Unable to update {image_uri}.") - return self._check_output_formatted( - ["docker", "run", "-d", "--rm", image_name, "tail", "-f", "/dev/null"] - ) + return self._check_output_formatted([ + "docker", + "run", + "-d", + "--rm", + image_name, + "tail", + "-f", + "/dev/null", + ]) def makedir(self, dir_path: str) -> None: """Creates a directory path in the container. @@ -188,13 +194,18 @@ def makedir(self, dir_path: str) -> None: subprocess.CalledProcessError: If unable to make the directory. """ try: - subprocess.check_output( - ["docker", "exec", self._container_name, "mkdir", "-p", dir_path] - ) + subprocess.check_output([ + "docker", + "exec", + self._container_name, + "mkdir", + "-p", + dir_path, + ]) except subprocess.CalledProcessError as e: output = e.output.decode("utf-8").strip() - self._logger.error(output) - raise e + self._logger.exception(output) + raise def copy_to(self, source: str, destination: str) -> None: """Copies a local file or directory to the container. @@ -208,16 +219,24 @@ def copy_to(self, source: str, destination: str) -> None: """ dirname = str(PurePosixPath(destination).parent) try: - subprocess.check_output( - ["docker", "exec", self._container_name, "mkdir", "-p", dirname] - ) - subprocess.check_output( - ["docker", "cp", source, f"{self._container_name}:{destination}"] - ) + subprocess.check_output([ + "docker", + "exec", + self._container_name, + "mkdir", + "-p", + dirname, + ]) + subprocess.check_output([ + "docker", + "cp", + source, + f"{self._container_name}:{destination}", + ]) except subprocess.CalledProcessError as e: output = e.output.decode("utf-8").strip() - self._logger.error(output) - raise e + self._logger.exception(output) + raise def copy_from(self, source: str, destination: str) -> None: """Copies a file or directory from the container locally. @@ -230,13 +249,16 @@ def copy_from(self, source: str, destination: str) -> None: subprocess.CalledProcessError: If unable to copy. """ try: - subprocess.check_output( - ["docker", "cp", f"{self._container_name}:{source}", destination] - ) + subprocess.check_output([ + "docker", + "cp", + f"{self._container_name}:{source}", + destination, + ]) except subprocess.CalledProcessError as e: output = e.output.decode("utf-8").strip() - self._logger.error(output) - raise e + self._logger.exception(output) + raise def run_local_job( self, @@ -251,9 +273,13 @@ def run_local_job( Raises: ValueError: `start_program_name` is not found. """ - start_program_name = self._check_output_formatted( - ["docker", "exec", self._container_name, "printenv", "SAGEMAKER_PROGRAM"] - ) + start_program_name = self._check_output_formatted([ + "docker", + "exec", + self._container_name, + "printenv", + "SAGEMAKER_PROGRAM", + ]) if not start_program_name: raise ValueError( "Start program not found. " @@ -263,16 +289,14 @@ def run_local_job( command = ["docker", "exec", "-w", self.CONTAINER_CODE_PATH] command.extend(self._envs_to_list(environment_variables)) - command.append(self._container_name) - command.append("python") - command.append(start_program_name) + command.extend((self._container_name, "python", start_program_name)) try: process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self.run_log = _stream_output(process) except Exception as e: self.run_log = e - self._logger.error(e) + self._logger.exception(e) # noqa: TRY401 def _end_session(self) -> None: """Stops and removes the local container.""" diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 65cef387c..c3e68c51e 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -181,7 +181,7 @@ def _copy_hyperparameters(container: _LocalJobContainer, **creation_kwargs: str) hyperparameters = creation_kwargs["hyperParameters"] with tempfile.TemporaryDirectory() as temp_dir: file_path = Path(temp_dir, "hyperparameters.json") - with open(file_path, "w") as write_file: + with open(file_path, "w", encoding="utf-8") as write_file: json.dump(hyperparameters, write_file) container.copy_to(str(file_path), "/opt/ml/input/config/hyperparameters.json") return True diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 9aa7dfaca..45951d646 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -import collections import os import sys from collections.abc import Generator @@ -21,13 +20,20 @@ # Support for reading logs # ############################################################################## -from typing import ClassVar, Optional +from typing import ClassVar, NamedTuple, Optional from botocore.exceptions import ClientError from braket.aws.aws_session import AwsSession +# Position is a tuple that includes the last read timestamp and the number of items that were read +# at that time. This is used to figure out which event to start with on the next read. +class Position(NamedTuple): + timestamp: str + skip: bool + + class ColorWrap: """A callable that prints text in a different color depending on the instance. Up to 5 if the standard output is a terminal or a Jupyter notebook cell. @@ -68,11 +74,6 @@ def _color_wrap(self, index: int, s: str) -> None: print(f"\x1b[{self._stream_colors[index % len(self._stream_colors)]}m{s}\x1b[0m") -# Position is a tuple that includes the last read timestamp and the number of items that were read -# at that time. This is used to figure out which event to start with on the next read. -Position = collections.namedtuple("Position", ["timestamp", "skip"]) - - def multi_stream_iter( aws_session: AwsSession, log_group: str, streams: list[str], positions: dict[str, Position] ) -> Generator[tuple[int, dict]]: @@ -99,7 +100,7 @@ def multi_stream_iter( for s in event_iters: try: events.append(next(s)) - except StopIteration: + except StopIteration: # noqa: PERF203 events.append(None) while any(events): @@ -149,12 +150,12 @@ def log_stream( events = events[skip:] skip = 0 else: - skip = skip - event_count + skip -= event_count events = [] yield from events -def flush_log_streams( # noqa C901 +def flush_log_streams( aws_session: AwsSession, log_group: str, stream_prefix: str, diff --git a/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py index 991370f35..5e58b4995 100644 --- a/src/braket/jobs/metrics.py +++ b/src/braket/jobs/metrics.py @@ -10,28 +10,28 @@ # 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. +from __future__ import annotations import time -from typing import Optional, Union def log_metric( metric_name: str, - value: Union[float, int], - timestamp: Optional[float] = None, - iteration_number: Optional[int] = None, + value: float, + timestamp: float | None = None, + iteration_number: int | None = None, ) -> None: """Records Braket Hybrid Job metrics. Args: metric_name (str): The name of the metric. - value (Union[float, int]): The value of the metric. + value (float): The value of the metric. - timestamp (Optional[float]): The time the metric data was received, expressed + timestamp (float | None): The time the metric data was received, expressed as the number of seconds since the epoch. Default: Current system time. - iteration_number (Optional[int]): The iteration number of the metric. + iteration_number (int | None): The iteration number of the metric. """ logged_timestamp = timestamp or time.time() metric_list = [f"Metrics - timestamp={logged_timestamp}; {metric_name}={value};"] diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index 8f5d3dcd5..f092163a9 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -15,7 +15,7 @@ import time from logging import Logger, getLogger -from typing import Any, Optional, Union +from typing import Any from braket.aws.aws_session import AwsSession from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType @@ -52,9 +52,7 @@ def __init__( self._logs_client = aws_session.logs_client @staticmethod - def _get_element_from_log_line( - element_name: str, log_line: list[dict[str, Any]] - ) -> Optional[str]: + def _get_element_from_log_line(element_name: str, log_line: list[dict[str, Any]]) -> str | None: """Finds and returns an element of a log line from CloudWatch Insights results. Args: @@ -85,12 +83,11 @@ def _get_metrics_results_sync(self, query_id: str) -> list[Any]: while time.time() < timeout_time: response = self._logs_client.get_query_results(queryId=query_id) query_status = response["status"] - if query_status in ["Failed", "Cancelled"]: + if query_status in {"Failed", "Cancelled"}: raise MetricsRetrievalError(f"Query {query_id} failed with status {query_status}.") - elif query_status == "Complete": + if query_status == "Complete": return response["results"] - else: - time.sleep(self._poll_interval_seconds) + time.sleep(self._poll_interval_seconds) self._logger.warning(f"Timed out waiting for query {query_id}.") return [] @@ -111,7 +108,7 @@ def _parse_log_line(self, result_entry: list[dict[str, Any]], parser: LogMetrics def _parse_log_query_results( self, results: list[Any], metric_type: MetricType, statistic: MetricStatistic - ) -> dict[str, list[Union[str, float, int]]]: + ) -> dict[str, list[str | float | int]]: """Parses CloudWatch Insights results and returns all found metrics. Args: @@ -137,7 +134,7 @@ def get_metrics_for_job( job_start_time: int | None = None, job_end_time: int | None = None, stream_prefix: str | None = None, - ) -> dict[str, list[Union[str, float, int]]]: + ) -> dict[str, list[str | float | int]]: """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index e8da4ff89..b6e331142 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -109,9 +109,7 @@ def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> list[s while time.time() < timeout_time: response = self._logs_client.describe_log_streams(**kwargs) if streams := response.get("logStreams"): - for stream in streams: - if name := stream.get("logStreamName"): - log_streams.append(name) + log_streams = [name for stream in streams if (name := stream.get("logStreamName"))] if next_token := response.get("nextToken"): kwargs["nextToken"] = next_token else: diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index 1ff5b4d49..0d30ef1ec 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -11,10 +11,11 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + import re from collections.abc import Iterator from logging import Logger, getLogger -from typing import Optional, Union from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType @@ -39,21 +40,21 @@ def __init__( @staticmethod def _get_value( - current_value: Optional[Union[str, float, int]], - new_value: Union[str, float, int], + current_value: str | float | None, + new_value: str | float, statistic: MetricStatistic, - ) -> Union[str, float, int]: + ) -> str | float: """Gets the value based on a statistic. Args: - current_value (Optional[Union[str, float, int]]): The current value. + current_value ( str | float | None): The current value. - new_value (Union[str, float, int]): The new value. + new_value ( str | float): The new value. statistic (MetricStatistic): The statistic to determine which value to use. Returns: - Union[str, float, int]: the value. + str | float: the value. """ if current_value is None: return new_value @@ -61,16 +62,14 @@ def _get_value( return max(current_value, new_value) return min(current_value, new_value) - def _get_metrics_from_log_line_matches( - self, all_matches: Iterator - ) -> dict[str, Union[str, float, int]]: + def _get_metrics_from_log_line_matches(self, all_matches: Iterator) -> dict[str | float]: """Converts matches from a RegEx to a set of metrics. Args: all_matches (Iterator): An iterator for RegEx matches on a log line. Returns: - dict[str, Union[str, float, int]]: The set of metrics found by the RegEx. The result + dict[str, | float]: The set of metrics found by the RegEx. The result is in the format { : }. This implies that multiple metrics with the same name are deduped to the last instance of that metric. """ @@ -107,7 +106,7 @@ def parse_log_message(self, timestamp: str, message: str) -> None: def get_columns_and_pivot_indices( self, pivot: str - ) -> tuple[dict[str, list[Union[str, float, int]]], dict[tuple[int, str], int]]: + ) -> tuple[dict[str, list[str | float]], dict[tuple[int, str], int]]: """Parses the metrics to find all the metrics that have the pivot column. The values of the pivot column are paired with the node_id and assigned a row index, so that all metrics with the same pivot value and node_id are stored in the same row. @@ -116,7 +115,7 @@ def get_columns_and_pivot_indices( pivot (str): The name of the pivot column. Must be TIMESTAMP or ITERATION_NUMBER. Returns: - tuple[dict[str, list[Union[str, float, int]]], dict[tuple[int, str], int]]: Contains: + tuple[dict[str, list[str | float]], dict[tuple[int, str], int]]: Contains: The dict[str, list[Any]] is the result table with all the metrics values initialized to None. The dict[tuple[int, str], int] is the list of pivot indices, where the value of a @@ -124,7 +123,7 @@ def get_columns_and_pivot_indices( """ row_count = 0 pivot_indices: dict[int, int] = {} - table: dict[str, list[Optional[Union[str, float, int]]]] = {} + table: dict[str, list[str | float | None]] = {} for metric in self.all_metrics: if pivot in metric: # If no node_id is present, pair pivot value with None for the key. @@ -140,7 +139,7 @@ def get_columns_and_pivot_indices( def get_metric_data_with_pivot( self, pivot: str, statistic: MetricStatistic - ) -> dict[str, list[Union[str, float, int]]]: + ) -> dict[str, list[str | float]]: """Gets the metric data for a given pivot column name. Metrics without the pivot column are not included in the results. Metrics that have the same value in the pivot column from the same node are returned in the same row. Metrics from different nodes are stored @@ -164,7 +163,7 @@ def get_metric_data_with_pivot( statistic (MetricStatistic): The statistic to determine which value to use. Returns: - dict[str, list[Union[str, float, int]]]: The metrics data. + dict[str, list[str | float]]: The metrics data. """ table, pivot_indices = self.get_columns_and_pivot_indices(pivot) for metric in self.all_metrics: @@ -179,7 +178,7 @@ def get_metric_data_with_pivot( def get_parsed_metrics( self, metric_type: MetricType, statistic: MetricStatistic - ) -> dict[str, list[Union[str, float, int]]]: + ) -> dict[str, list[str | float]]: """Gets all the metrics data, where the keys are the column names and the values are a list containing the values in each row. @@ -190,7 +189,7 @@ def get_parsed_metrics( when there is a conflict. Returns: - dict[str, list[Union[str, float, int]]]: The metrics data. + dict[str, list[str | float]]: The metrics data. Example: timestamp energy diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 3c4a01b5c..75ce4437e 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -279,8 +279,8 @@ def _generate_default_job_name( elif not image_uri: name = "braket-job-default" else: - job_type_match = re.search("/amazon-braket-(.*)-jobs:", image_uri) or re.search( - "/amazon-braket-([^:/]*)", image_uri + job_type_match = re.search(r"/amazon-braket-(.*)-jobs:", image_uri) or re.search( + r"/amazon-braket-([^:/]*)", image_uri ) container = f"-{job_type_match.groups()[0]}" if job_type_match else "" name = f"braket-job{container}" @@ -361,7 +361,7 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: importlib.invalidate_caches() module = importlib.util.find_spec(importable, source_module_path.stem) if module is None: - raise AssertionError + raise AssertionError # noqa: TRY301 except (ModuleNotFoundError, AssertionError) as e: raise ValueError(f"Entry point module was not found: {importable}") from e finally: diff --git a/src/braket/jobs/serialization.py b/src/braket/jobs/serialization.py index 179a44970..c28510494 100644 --- a/src/braket/jobs/serialization.py +++ b/src/braket/jobs/serialization.py @@ -59,7 +59,7 @@ def deserialize_values( the specified `data_format` to plaintext. """ return ( - {k: pickle.loads(codecs.decode(v.encode(), "base64")) for k, v in data_dictionary.items()} + {k: pickle.loads(codecs.decode(v.encode(), "base64")) for k, v in data_dictionary.items()} # noqa: S301 if data_format == PersistedJobDataFormat.PICKLED_V4 else data_dictionary ) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 3ef188004..7db9f7012 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -14,7 +14,6 @@ from __future__ import annotations from numbers import Number -from typing import Union from sympy import Symbol @@ -118,7 +117,7 @@ def name(self) -> str: """str: Name of this parameter.""" return self._name.name - def subs(self, parameter_values: dict[str, Number]) -> Union[FreeParameter, Number]: + def subs(self, parameter_values: dict[str, Number]) -> FreeParameter | Number: """Substitutes a value in if the parameter exists within the mapping. Args: diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index fdd2f5474..43221d35f 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -17,7 +17,7 @@ import operator from functools import reduce from numbers import Number -from typing import Any, Union +from typing import Any import sympy from oqpy.base import OQPyExpression @@ -33,7 +33,7 @@ class FreeParameterExpression: present will NOT run. Values must be substituted prior to execution. """ - def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr, str]): + def __init__(self, expression: FreeParameterExpression | Number | sympy.Expr | str): """Initializes a FreeParameterExpression. Best practice is to initialize using FreeParameters and Numbers. Not meant to be initialized directly. @@ -67,7 +67,7 @@ def __init__(self, expression: Union[FreeParameterExpression, Number, sympy.Expr raise NotImplementedError @property - def expression(self) -> Union[Number, sympy.Expr]: + def expression(self) -> Number | sympy.Expr: """Gets the expression. Returns: @@ -77,7 +77,7 @@ def expression(self) -> Union[Number, sympy.Expr]: def subs( self, parameter_values: dict[str, Number] - ) -> Union[FreeParameterExpression, Number, sympy.Expr]: + ) -> FreeParameterExpression | Number | sympy.Expr: """ Similar to a substitution in Sympy. Parameters are swapped for corresponding values or expressions from the dictionary. @@ -100,8 +100,7 @@ def subs( subbed_expr = self._expression.subs(new_parameter_values) if isinstance(subbed_expr, Number): return subbed_expr - else: - return FreeParameterExpression(subbed_expr) + return FreeParameterExpression(subbed_expr) def _parse_string_expression(self, expression: str) -> FreeParameterExpression: return self._eval_operation(ast.parse(expression, mode="eval").body) @@ -109,26 +108,24 @@ def _parse_string_expression(self, expression: str) -> FreeParameterExpression: def _eval_operation(self, node: Any) -> FreeParameterExpression: if isinstance(node, ast.Constant): return FreeParameterExpression(node.n) - elif isinstance(node, ast.Name): + if isinstance(node, ast.Name): return FreeParameterExpression(sympy.Symbol(node.id)) - elif isinstance(node, ast.BinOp): - if type(node.op) not in self._operations.keys(): + if isinstance(node, ast.BinOp): + if type(node.op) not in self._operations: raise ValueError(f"Unsupported binary operation: {type(node.op)}") return self._eval_operation(node.left)._operations[type(node.op)]( self._eval_operation(node.right) ) - elif isinstance(node, ast.UnaryOp): - if type(node.op) not in self._operations.keys(): + if isinstance(node, ast.UnaryOp): + if type(node.op) not in self._operations: raise ValueError(f"Unsupported unary operation: {type(node.op)}", type(node.op)) return self._eval_operation(node.operand)._operations[type(node.op)]() - else: - raise ValueError(f"Unsupported string detected: {node}") + raise ValueError(f"Unsupported string detected: {node}") def __add__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression + other.expression) - else: - return FreeParameterExpression(self.expression + other) + return FreeParameterExpression(self.expression + other) def __radd__(self, other: FreeParameterExpression): return FreeParameterExpression(other + self.expression) @@ -136,8 +133,7 @@ def __radd__(self, other: FreeParameterExpression): def __sub__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression - other.expression) - else: - return FreeParameterExpression(self.expression - other) + return FreeParameterExpression(self.expression - other) def __rsub__(self, other: FreeParameterExpression): return FreeParameterExpression(other - self.expression) @@ -145,26 +141,23 @@ def __rsub__(self, other: FreeParameterExpression): def __mul__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression * other.expression) - else: - return FreeParameterExpression(self.expression * other) + return FreeParameterExpression(self.expression * other) def __rmul__(self, other: FreeParameterExpression): return FreeParameterExpression(other * self.expression) - def __truediv__(self, other): + def __truediv__(self, other: FreeParameterExpression): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression / other.expression) - else: - return FreeParameterExpression(self.expression / other) + return FreeParameterExpression(self.expression / other) def __rtruediv__(self, other: FreeParameterExpression): return FreeParameterExpression(other / self.expression) - def __pow__(self, other: FreeParameterExpression, modulo: float = None): + def __pow__(self, other: FreeParameterExpression, modulo: float | None = None): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) - else: - return FreeParameterExpression(self.expression**other) + return FreeParameterExpression(self.expression**other) def __rpow__(self, other: FreeParameterExpression): return FreeParameterExpression(other**self.expression) @@ -195,22 +188,17 @@ def _to_oqpy_expression(self) -> OQPyExpression: if isinstance(self.expression, tuple(ops)): return reduce( ops[type(self.expression)], - map( - lambda x: FreeParameterExpression(x)._to_oqpy_expression(), self.expression.args - ), + (FreeParameterExpression(x)._to_oqpy_expression() for x in self.expression.args), ) - elif isinstance(self.expression, sympy.Number): + if isinstance(self.expression, sympy.Number): return float(self.expression) - else: - fvar = FloatVar( - name=self.expression.name, init_expression="input", needs_declaration=False - ) - fvar.size = None - fvar.type.size = None - return fvar + fvar = FloatVar(name=self.expression.name, init_expression="input", needs_declaration=False) + fvar.size = None + fvar.type.size = None + return fvar -def subs_if_free_parameter(parameter: Any, **kwargs: Union[FreeParameterExpression, str]) -> Any: +def subs_if_free_parameter(parameter: Any, **kwargs: FreeParameterExpression | str) -> Any: """Substitute a free parameter with the given kwargs, if any. Args: @@ -240,6 +228,7 @@ def _is_float(argument: str) -> bool: """ try: float(argument) - return True except ValueError: return False + else: + return True diff --git a/src/braket/parametric/parameterizable.py b/src/braket/parametric/parameterizable.py index 90c4dc589..fd59c6782 100644 --- a/src/braket/parametric/parameterizable.py +++ b/src/braket/parametric/parameterizable.py @@ -14,7 +14,7 @@ from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Union +from typing import Any from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import FreeParameterExpression @@ -27,7 +27,7 @@ class Parameterizable(ABC): @property @abstractmethod - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + def parameters(self) -> list[FreeParameterExpression | FreeParameter | float]: """Get the parameters. Returns: @@ -37,7 +37,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float """ @abstractmethod - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Any: + def bind_values(self, **kwargs: FreeParameter | str) -> Any: """Takes in parameters and returns an object with specified parameters replaced with their values. diff --git a/src/braket/pulse/__init__.py b/src/braket/pulse/__init__.py index 48f7022af..e19aad248 100644 --- a/src/braket/pulse/__init__.py +++ b/src/braket/pulse/__init__.py @@ -14,10 +14,10 @@ from braket.pulse.frame import Frame # noqa: F401 from braket.pulse.port import Port # noqa: F401 from braket.pulse.pulse_sequence import PulseSequence # noqa: F401 -from braket.pulse.waveforms import ( # noqa: F401 - ArbitraryWaveform, - ConstantWaveform, - DragGaussianWaveform, - ErfSquareWaveform, - GaussianWaveform, +from braket.pulse.waveforms import ( + ArbitraryWaveform, # noqa: F401 + ConstantWaveform, # noqa: F401 + DragGaussianWaveform, # noqa: F401 + ErfSquareWaveform, # noqa: F401 + GaussianWaveform, # noqa: F401 ) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 16c9c6726..9442f5772 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -49,7 +49,7 @@ class _ParseState: frame_data: dict[str, _FrameState] -class _ApproximationParser(QASMVisitor[_ParseState]): +class _ApproximationParser(QASMVisitor[_ParseState]): # noqa: PLR0904 """Walk the AST and build the output signal amplitude, frequency and phases for each channel. """ @@ -146,13 +146,13 @@ def visit_ClassicalDeclaration( Returns: Union[dict, None]: Returns a dict if WaveformType, None otherwise. - """ + """ # noqa: DOC202 identifier = self.visit(node.identifier, context) - if type(node.type) == ast.WaveformType: + if type(node.type) is ast.WaveformType: context.variables[identifier] = self.visit(node.init_expression, context) - elif type(node.type) == ast.FrameType: + elif type(node.type) is ast.FrameType: pass - elif type(node.type) != ast.PortType: + elif type(node.type) is not ast.PortType: raise NotImplementedError def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: @@ -188,9 +188,6 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - Args: node (ast.QuantumBarrier): The quantum barrier. context (_ParseState): The parse state context. - - Returns: - None: No return value. """ frames = self._get_frame_parameters(node.qubits, context) if len(frames) == 0: @@ -234,8 +231,7 @@ def visit_Identifier(self, node: ast.Identifier, context: _ParseState) -> Any: """ if node.name in context.variables: return context.variables[node.name] - else: - return node.name + return node.name def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) -> bool: """Visit Unary Expression. @@ -254,15 +250,13 @@ def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) """ if node.op == ast.UnaryOperator["-"]: return -1 * self.visit(node.expression, context) - elif node.op == ast.UnaryOperator["!"]: + if node.op == ast.UnaryOperator["!"]: return not self.visit(node.expression, context) - elif node.op == ast.UnaryOperator["~"]: + if node.op == ast.UnaryOperator["~"]: return ~self.visit(node.expression, context) - else: - raise NotImplementedError + raise NotImplementedError - # flake8: noqa: C901 - def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: + def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: # noqa: C901, PLR0911, PLR0912 """Visit Binary Expression. node.lhs, node.rhs, node.op 1+2 @@ -287,44 +281,43 @@ def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseStat if node.op == op["+"]: return lhs + rhs - elif node.op == op["-"]: + if node.op == op["-"]: return lhs - rhs - elif node.op == op["*"]: + if node.op == op["*"]: return lhs * rhs - elif node.op == op["/"]: + if node.op == op["/"]: return lhs / rhs - elif node.op == op["%"]: + if node.op == op["%"]: return lhs % rhs - elif node.op == op["**"]: + if node.op == op["**"]: return lhs**rhs - elif node.op == op[">"]: + if node.op == op[">"]: return lhs > rhs - elif node.op == op["<"]: + if node.op == op["<"]: return lhs < rhs - elif node.op == op[">="]: + if node.op == op[">="]: return lhs >= rhs - elif node.op == op["<="]: + if node.op == op["<="]: return lhs <= rhs - elif node.op == op["=="]: + if node.op == op["=="]: return lhs == rhs - elif node.op == op["!="]: + if node.op == op["!="]: return lhs != rhs - elif node.op == op["&&"]: + if node.op == op["&&"]: return lhs and rhs - elif node.op == op["||"]: + if node.op == op["||"]: return lhs or rhs - elif node.op == op["|"]: + if node.op == op["|"]: return lhs | rhs - elif node.op == op["^"]: + if node.op == op["^"]: return lhs ^ rhs - elif node.op == op["&"]: + if node.op == op["&"]: return lhs & rhs - elif node.op == op["<<"]: + if node.op == op["<<"]: return lhs << rhs - elif node.op == op[">>"]: + if node.op == op[">>"]: return lhs >> rhs - else: - raise NotImplementedError + raise NotImplementedError def visit_ArrayLiteral(self, node: ast.ArrayLiteral, context: _ParseState) -> list[Any]: """Visit Array Literal. @@ -501,9 +494,6 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: Raises: NotImplementedError: Raises if not of type [ast.Identifier, ast.FunctionCall, ast.ArrayLiteral] - - Returns: - None: Returns None """ frame_id = self.visit(node.arguments[0], context) if isinstance(node.arguments[1], ast.ArrayLiteral): @@ -513,7 +503,7 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: if isinstance(amps, Waveform): amps = amps.sample(context.frame_data[frame_id].dt) elif isinstance(amps, str): - raise NameError(f"waveform '{amps}' is not defined.") + raise NameError(f"waveform '{amps}' is not defined.") # noqa: TRY004 else: raise NotImplementedError frame_data = context.frame_data[frame_id] @@ -579,11 +569,10 @@ def erf_square(self, node: ast.FunctionCall, context: _ParseState) -> Waveform: def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: - frame_states = { + return { frameId: _FrameState(frame.port.dt, frame.frequency, frame.phase % (2 * np.pi)) for frameId, frame in frames.items() } - return frame_states def _init_qubit_frame_mapping(frames: dict[str, Frame]) -> dict[str, list[str]]: diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 1750275ac..ce201f3c1 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -69,7 +69,7 @@ def visit_BinaryExpression( } if isinstance(rhs, ast.FloatLiteral): return ast.FloatLiteral(ops[node.op](lhs.value, rhs.value)) - elif isinstance(rhs, ast.DurationLiteral) and node.op == ast.BinaryOperator["*"]: + if isinstance(rhs, ast.DurationLiteral) and node.op == ast.BinaryOperator["*"]: return OQDurationLiteral(lhs.value * rhs.value).to_ast(self.program) return ast.BinaryExpression(op=node.op, lhs=lhs, rhs=rhs) diff --git a/src/braket/pulse/frame.py b/src/braket/pulse/frame.py index 63700d22e..e5c7893c5 100644 --- a/src/braket/pulse/frame.py +++ b/src/braket/pulse/frame.py @@ -14,7 +14,7 @@ from __future__ import annotations import math -from typing import Any, Optional +from typing import Any from oqpy import FrameVar as OQFrame from oqpy.base import OQPyExpression @@ -35,7 +35,7 @@ def __init__( frequency: float, phase: float = 0, is_predefined: bool = False, - properties: Optional[dict[str, Any]] = None, + properties: dict[str, Any] | None = None, ): """Initializes a Frame. diff --git a/src/braket/pulse/port.py b/src/braket/pulse/port.py index 99b1acca5..b4122371c 100644 --- a/src/braket/pulse/port.py +++ b/src/braket/pulse/port.py @@ -13,7 +13,7 @@ from __future__ import annotations -from typing import Any, Optional +from typing import Any from oqpy import PortVar from oqpy.base import OQPyExpression @@ -24,7 +24,7 @@ class Port: a device. See https://openqasm.com/language/openpulse.html#ports for more details. """ - def __init__(self, port_id: str, dt: float, properties: Optional[dict[str, Any]] = None): + def __init__(self, port_id: str, dt: float, properties: dict[str, Any] | None = None): """Initializes a Port. Args: diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index 6c5d60444..59afceaac 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -16,7 +16,7 @@ import builtins from copy import deepcopy from inspect import signature -from typing import Any, Union +from typing import Any from openpulse import ast from oqpy import BitVar, PhysicalQubits, Program @@ -68,7 +68,7 @@ def parameters(self) -> set[FreeParameter]: return self._free_parameters.copy() def set_frequency( - self, frame: Frame, frequency: Union[float, FreeParameterExpression] + self, frame: Frame, frequency: float | FreeParameterExpression ) -> PulseSequence: """Adds an instruction to set the frequency of the frame to the specified `frequency` value. @@ -87,7 +87,7 @@ def set_frequency( return self def shift_frequency( - self, frame: Frame, frequency: Union[float, FreeParameterExpression] + self, frame: Frame, frequency: float | FreeParameterExpression ) -> PulseSequence: """Adds an instruction to shift the frequency of the frame by the specified `frequency` value. @@ -106,9 +106,7 @@ def shift_frequency( self._frames[frame.id] = frame return self - def set_phase( - self, frame: Frame, phase: Union[float, FreeParameterExpression] - ) -> PulseSequence: + def set_phase(self, frame: Frame, phase: float | FreeParameterExpression) -> PulseSequence: """Adds an instruction to set the phase of the frame to the specified `phase` value. Args: @@ -125,9 +123,7 @@ def set_phase( self._frames[frame.id] = frame return self - def shift_phase( - self, frame: Frame, phase: Union[float, FreeParameterExpression] - ) -> PulseSequence: + def shift_phase(self, frame: Frame, phase: float | FreeParameterExpression) -> PulseSequence: """Adds an instruction to shift the phase of the frame by the specified `phase` value. Args: @@ -164,9 +160,7 @@ def swap_phases( self._frames[frame_2.id] = frame_2 return self - def set_scale( - self, frame: Frame, scale: Union[float, FreeParameterExpression] - ) -> PulseSequence: + def set_scale(self, frame: Frame, scale: float | FreeParameterExpression) -> PulseSequence: """Adds an instruction to set the scale on the frame to the specified `scale` value. Args: @@ -185,8 +179,8 @@ def set_scale( def delay( self, - qubits_or_frames: Union[Frame, list[Frame], QubitSet], - duration: Union[float, FreeParameterExpression], + qubits_or_frames: Frame | list[Frame] | QubitSet, + duration: float | FreeParameterExpression, ) -> PulseSequence: """Adds an instruction to advance the frame clock by the specified `duration` value. @@ -212,7 +206,7 @@ def delay( self._program.delay(time=duration, qubits_or_frames=physical_qubits) return self - def barrier(self, qubits_or_frames: Union[list[Frame], QubitSet]) -> PulseSequence: + def barrier(self, qubits_or_frames: list[Frame] | QubitSet) -> PulseSequence: """Adds an instruction to align the frame clocks to the latest time across all the specified frames. @@ -349,7 +343,7 @@ def to_ir(self, sort_input_parameters: bool = False) -> str: def _register_free_parameters( self, - parameter: Union[float, FreeParameterExpression], + parameter: float | FreeParameterExpression, ) -> None: if isinstance(parameter, FreeParameterExpression) and isinstance( parameter.expression, Expr @@ -367,14 +361,13 @@ def _parse_arg_from_calibration_schema( } if argument["type"] in nonprimitive_arg_type: return nonprimitive_arg_type[argument["type"]](argument["value"]) - else: - return getattr(builtins, argument["type"])(argument["value"]) + return getattr(builtins, argument["type"])(argument["value"]) @classmethod def _parse_from_calibration_schema( cls, calibration: dict, waveforms: dict[Waveform], frames: dict[Frame] ) -> PulseSequence: - """Parsing a JSON input based on https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L26. # noqa: E501 + """Parsing a JSON input based on https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L26. Args: calibration (dict): The pulse instruction to parse @@ -422,9 +415,7 @@ def _parse_from_calibration_schema( instr_function(**instr_args) return calibration_sequence - def __call__( - self, arg: Any | None = None, **kwargs: Union[FreeParameter, str] - ) -> PulseSequence: + def __call__(self, arg: Any | None = None, **kwargs: FreeParameter | str) -> PulseSequence: """Implements the call function to easily make a bound PulseSequence. Args: @@ -453,7 +444,7 @@ def __eq__(self, other: PulseSequence): def _validate_uniqueness( - mapping: dict[str, Any], values: Union[Frame, Waveform, list[Frame], list[Waveform]] + mapping: dict[str, Any], values: Frame | Waveform | list[Frame] | list[Waveform] ) -> None: if not isinstance(values, list): values = [values] diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 9da7bee4c..bc3878698 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -16,7 +16,6 @@ import random import string from abc import ABC, abstractmethod -from typing import Optional, Union import numpy as np import scipy as sp @@ -58,7 +57,7 @@ def sample(self, dt: float) -> np.ndarray: @staticmethod @abstractmethod def _from_calibration_schema(waveform_json: dict) -> Waveform: - """Parses a JSON input and returns the BDK waveform. See https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L104 # noqa: E501 + """Parses a JSON input and returns the BDK waveform. See https://github.com/aws/amazon-braket-schemas-python/blob/main/src/braket/device_schema/pulse/native_gate_calibrations_v1.py#L104 Args: waveform_json (dict): A JSON object with the needed parameters for making the Waveform. @@ -73,7 +72,7 @@ class ArbitraryWaveform(Waveform): an array. """ - def __init__(self, amplitudes: list[complex], id: Optional[str] = None): + def __init__(self, amplitudes: list[complex], id: str | None = None): """Initializes an `ArbitraryWaveform`. Args: @@ -129,9 +128,7 @@ class ConstantWaveform(Waveform, Parameterizable): specified length. """ - def __init__( - self, length: Union[float, FreeParameterExpression], iq: complex, id: Optional[str] = None - ): + def __init__(self, length: float | FreeParameterExpression, iq: complex, id: str | None = None): """Initializes a `ConstantWaveform`. Args: @@ -149,7 +146,7 @@ def __repr__(self) -> str: return f"ConstantWaveform('id': {self.id}, 'length': {self.length}, 'iq': {self.iq})" @property - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + def parameters(self) -> list[FreeParameterExpression | FreeParameter | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. @@ -158,7 +155,7 @@ def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float """ return [self.length] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ConstantWaveform: + def bind_values(self, **kwargs: FreeParameter | str) -> ConstantWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. @@ -207,8 +204,7 @@ def sample(self, dt: float) -> np.ndarray: """ # Amplitudes should be gated by [0:self.length] sample_range = np.arange(0, self.length, dt) - samples = self.iq * np.ones_like(sample_range) - return samples + return self.iq * np.ones_like(sample_range) @staticmethod def _from_calibration_schema(waveform_json: dict) -> ConstantWaveform: @@ -235,12 +231,12 @@ class DragGaussianWaveform(Waveform, Parameterizable): def __init__( self, - length: Union[float, FreeParameterExpression], - sigma: Union[float, FreeParameterExpression], - beta: Union[float, FreeParameterExpression], - amplitude: Union[float, FreeParameterExpression] = 1, + length: float | FreeParameterExpression, + sigma: float | FreeParameterExpression, + beta: float | FreeParameterExpression, + amplitude: float | FreeParameterExpression = 1, zero_at_edges: bool = False, - id: Optional[str] = None, + id: str | None = None, ): """Initializes a `DragGaussianWaveform`. @@ -272,13 +268,13 @@ def __repr__(self) -> str: ) @property - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + def parameters(self) -> list[FreeParameterExpression | FreeParameter | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. """ return [self.length, self.sigma, self.beta, self.amplitude] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> DragGaussianWaveform: + def bind_values(self, **kwargs: FreeParameter | str) -> DragGaussianWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. @@ -347,7 +343,7 @@ def sample(self, dt: float) -> np.ndarray: sample_range = np.arange(0, self.length, dt) t0 = self.length / 2 zero_at_edges_int = int(self.zero_at_edges) - samples = ( + return ( (1 - (1.0j * self.beta * ((sample_range - t0) / self.sigma**2))) * ( self.amplitude @@ -358,7 +354,6 @@ def sample(self, dt: float) -> np.ndarray: - zero_at_edges_int * np.exp(-0.5 * ((self.length / (2 * self.sigma)) ** 2)) ) ) - return samples @staticmethod def _from_calibration_schema(waveform_json: dict) -> DragGaussianWaveform: @@ -377,11 +372,11 @@ class GaussianWaveform(Waveform, Parameterizable): def __init__( self, - length: Union[float, FreeParameterExpression], - sigma: Union[float, FreeParameterExpression], - amplitude: Union[float, FreeParameterExpression] = 1, + length: float | FreeParameterExpression, + sigma: float | FreeParameterExpression, + amplitude: float | FreeParameterExpression = 1, zero_at_edges: bool = False, - id: Optional[str] = None, + id: str | None = None, ): """Initializes a `GaussianWaveform`. @@ -410,13 +405,13 @@ def __repr__(self) -> str: ) @property - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + def parameters(self) -> list[FreeParameterExpression | FreeParameter | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. """ return [self.length, self.sigma, self.amplitude] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> GaussianWaveform: + def bind_values(self, **kwargs: FreeParameter | str) -> GaussianWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. @@ -481,14 +476,13 @@ def sample(self, dt: float) -> np.ndarray: sample_range = np.arange(0, self.length, dt) t0 = self.length / 2 zero_at_edges_int = int(self.zero_at_edges) - samples = ( + return ( self.amplitude / (1 - zero_at_edges_int * np.exp(-0.5 * ((self.length / (2 * self.sigma)) ** 2))) ) * ( np.exp(-0.5 * (((sample_range - t0) / self.sigma) ** 2)) - zero_at_edges_int * np.exp(-0.5 * ((self.length / (2 * self.sigma)) ** 2)) ) - return samples @staticmethod def _from_calibration_schema(waveform_json: dict) -> GaussianWaveform: @@ -507,13 +501,13 @@ class ErfSquareWaveform(Waveform, Parameterizable): def __init__( self, - length: Union[float, FreeParameterExpression], - width: Union[float, FreeParameterExpression], - sigma: Union[float, FreeParameterExpression], - off_center: Union[float, FreeParameterExpression] = 0, - amplitude: Union[float, FreeParameterExpression] = 1, + length: float | FreeParameterExpression, + width: float | FreeParameterExpression, + sigma: float | FreeParameterExpression, + off_center: float | FreeParameterExpression = 0, + amplitude: float | FreeParameterExpression = 1, zero_at_edges: bool = False, - id: Optional[str] = None, + id: str | None = None, ): r"""Initializes a `ErfSquareWaveform`. @@ -524,20 +518,20 @@ def __init__( height. The waveform is scaled such that its maximum is equal to `amplitude`. Args: - length (Union[float, FreeParameterExpression]): Duration (in seconds) from the start + length (float | FreeParameterExpression): Duration (in seconds) from the start to the end of the waveform. - width (Union[float, FreeParameterExpression]): Duration (in seconds) between the + width (float | FreeParameterExpression): Duration (in seconds) between the half height of the two edges. - sigma (Union[float, FreeParameterExpression]): A characteristic time of how quickly + sigma (float | FreeParameterExpression): A characteristic time of how quickly the edges rise and fall. - off_center (Union[float, FreeParameterExpression]): Shift the smoothed square waveform + off_center (float | FreeParameterExpression): Shift the smoothed square waveform earlier or later in time. When positive, the smoothed square is shifted later (to the right), otherwise earlier (to the left). Defaults to 0. - amplitude (Union[float, FreeParameterExpression]): The amplitude of the waveform + amplitude (float | FreeParameterExpression): The amplitude of the waveform envelope. Defaults to 1. zero_at_edges (bool): Whether the waveform is scaled such that it has zero value at the edges. Defaults to False. - id (Optional[str]): The identifier used for declaring this waveform. A random string of + id (str | None): The identifier used for declaring this waveform. A random string of ascii characters is assigned by default. """ self.length = length @@ -556,18 +550,18 @@ def __repr__(self) -> str: ) @property - def parameters(self) -> list[Union[FreeParameterExpression, FreeParameter, float]]: + def parameters(self) -> list[FreeParameterExpression | FreeParameter | float]: """Returns the parameters associated with the object, either unbound free parameter expressions or bound values. """ return [self.length, self.width, self.sigma, self.off_center, self.amplitude] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> ErfSquareWaveform: + def bind_values(self, **kwargs: FreeParameter | str) -> ErfSquareWaveform: """Takes in parameters and returns an object with specified parameters replaced with their values. Args: - **kwargs (Union[FreeParameter, str]): Arbitrary keyword arguments. + **kwargs (FreeParameter | str): Arbitrary keyword arguments. Returns: ErfSquareWaveform: A copy of this waveform with the requested parameters bound. @@ -657,8 +651,7 @@ def sample(self, dt: float) -> np.ndarray: / (mid_waveform_height - waveform_bottom) * self.amplitude ) - else: - return samples * self.amplitude / mid_waveform_height + return samples * self.amplitude / mid_waveform_height @staticmethod def _from_calibration_schema(waveform_json: dict) -> ErfSquareWaveform: @@ -673,7 +666,7 @@ def _from_calibration_schema(waveform_json: dict) -> ErfSquareWaveform: def _make_identifier_name() -> str: - return "".join([random.choice(string.ascii_letters) for _ in range(10)]) # noqa S311 + return "".join([random.choice(string.ascii_letters) for _ in range(10)]) # noqa: S311 def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 0de8e8e3b..723eb94bd 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -14,7 +14,6 @@ from __future__ import annotations import itertools -from typing import Optional, Union from braket.circuits.circuit import Circuit from braket.circuits.observables import I, TensorProduct, X, Y, Z @@ -37,7 +36,7 @@ class PauliString: """A lightweight representation of a Pauli string with its phase.""" - def __init__(self, pauli_string: Union[str, PauliString]): + def __init__(self, pauli_string: str | PauliString): """Initializes a `PauliString`. Args: @@ -90,19 +89,17 @@ def to_unsigned_observable(self, include_trivial: bool = False) -> TensorProduct TensorProduct: The tensor product of the unsigned factors in the Pauli string. """ if include_trivial: - return TensorProduct( - [ - ( - _PAULI_OBSERVABLES[self._nontrivial[qubit]] - if qubit in self._nontrivial - else _ID_OBS - ) - for qubit in range(self._qubit_count) - ] - ) - return TensorProduct( - [_PAULI_OBSERVABLES[self._nontrivial[qubit]] for qubit in sorted(self._nontrivial)] - ) + return TensorProduct([ + ( + _PAULI_OBSERVABLES[self._nontrivial[qubit]] + if qubit in self._nontrivial + else _ID_OBS + ) + for qubit in range(self._qubit_count) + ]) + return TensorProduct([ + _PAULI_OBSERVABLES[self._nontrivial[qubit]] for qubit in sorted(self._nontrivial) + ]) def weight_n_substrings(self, weight: int) -> tuple[PauliString, ...]: r"""Returns every substring of this Pauli string with exactly `weight` nontrivial factors. @@ -131,7 +128,7 @@ def weight_n_substrings(self, weight: int) -> tuple[PauliString, ...]: ) return tuple(substrings) - def eigenstate(self, signs: Optional[Union[str, list[int], tuple[int, ...]]] = None) -> Circuit: + def eigenstate(self, signs: str | list[int] | tuple[int, ...] | None = None) -> Circuit: """Returns the eigenstate of this Pauli string with the given factor signs. The resulting eigenstate has each qubit in the +1 eigenstate of its corresponding signed @@ -271,7 +268,7 @@ def power(self, n: int, inplace: bool = False) -> PauliString: ValueError: If `n` isn't a plain Python `int`. """ if not isinstance(n, int): - raise ValueError("Must be raised to integer power") + raise TypeError("Must be raised to integer power") # Since pauli ops involutory, result is either identity or unchanged pauli_other = PauliString(self) @@ -363,10 +360,7 @@ def __len__(self): return self._qubit_count def __repr__(self): - factors = [ - self._nontrivial[qubit] if qubit in self._nontrivial else "I" - for qubit in range(self._qubit_count) - ] + factors = [self._nontrivial.get(qubit, "I") for qubit in range(self._qubit_count)] return f"{PauliString._phase_to_str(self._phase)}{''.join(factors)}" @staticmethod diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py index e7bed6aa8..f8a6c7e45 100644 --- a/src/braket/registers/qubit_set.py +++ b/src/braket/registers/qubit_set.py @@ -50,20 +50,18 @@ def __init__(self, qubits: QubitSetInput | None = None): >>> qubits = QubitSet([0, 1]) >>> for qubit in qubits: ... print(qubit) - ... Qubit(0) Qubit(1) >>> qubits = QubitSet([0, 1, [2, 3]]) >>> for qubit in qubits: ... print(qubit) - ... Qubit(0) Qubit(1) Qubit(2) Qubit(3) """ - _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] if qubits is not None else None + _qubits = [Qubit.new(qubit) for qubit in _flatten(qubits)] if qubits is not None else None # noqa: RUF052 super().__init__(_qubits) def map(self, mapping: dict[QubitInput, QubitInput]) -> QubitSet: @@ -83,7 +81,7 @@ def map(self, mapping: dict[QubitInput, QubitInput]) -> QubitSet: >>> mapping = {0: 10, Qubit(1): Qubit(11)} >>> qubits.map(mapping) QubitSet([Qubit(10), Qubit(11)]) - """ # noqa E501 + """ # noqa: E501 new_qubits = [mapping.get(qubit, qubit) for qubit in self] return QubitSet(new_qubits) diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index d40b0547c..1df9081c0 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -18,8 +18,8 @@ ) from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult # noqa: F401 from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult # noqa: F401 -from braket.tasks.photonic_model_quantum_task_result import ( # noqa: F401 - PhotonicModelQuantumTaskResult, +from braket.tasks.photonic_model_quantum_task_result import ( + PhotonicModelQuantumTaskResult, # noqa: F401 ) from braket.tasks.quantum_task import QuantumTask # noqa: F401 from braket.tasks.quantum_task_batch import QuantumTaskBatch # noqa: F401 diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index 7bfb57eb3..8f72e7465 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -149,9 +149,7 @@ def get_avg_density(self) -> np.ndarray: N_ryd_cnt = np.sum(N_ryd, axis=0) N_ground_cnt = np.sum(N_ground, axis=0) - avg_density = N_ryd_cnt / (N_ryd_cnt + N_ground_cnt) - - return avg_density + return N_ryd_cnt / (N_ryd_cnt + N_ground_cnt) def _equal_sequences(sequence0: np.ndarray, sequence1: np.ndarray) -> bool: diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index bf5e9900d..31b055cbc 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -15,7 +15,6 @@ from collections.abc import Generator from dataclasses import dataclass -from typing import Optional import numpy as np @@ -47,7 +46,7 @@ class AnnealingQuantumTaskResult: def data( self, - selected_fields: Optional[list[str]] = None, + selected_fields: list[str] | None = None, sorted_by: str = "value", reverse: bool = False, ) -> Generator[tuple]: diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index e8267032f..577a74d1b 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -14,10 +14,12 @@ from __future__ import annotations import json +import operator from collections import Counter from collections.abc import Callable from dataclasses import dataclass -from typing import Any, Optional, TypeVar, Union +from itertools import starmap +from typing import Any, TypeVar import numpy as np @@ -132,7 +134,7 @@ def __eq__(self, other: GateModelQuantumTaskResult) -> bool: return self.task_metadata.id == other.task_metadata.id return NotImplemented - def get_compiled_circuit(self) -> Optional[str]: + def get_compiled_circuit(self) -> str | None: """Get the compiled circuit, if one is available. Returns: @@ -143,12 +145,11 @@ def get_compiled_circuit(self) -> Optional[str]: return None if metadata.rigettiMetadata: return metadata.rigettiMetadata.compiledProgram - elif metadata.oqcMetadata: + if metadata.oqcMetadata: return metadata.oqcMetadata.compiledProgram - elif metadata.iqmMetadata: + if metadata.iqmMetadata: return metadata.iqmMetadata.compiledProgram - else: - return None + return None @staticmethod def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: @@ -183,10 +184,7 @@ def measurement_probabilities_from_measurement_counts( """ shots = sum(measurement_counts.values()) - measurement_probabilities = { - key: count / shots for key, count in measurement_counts.items() - } - return measurement_probabilities + return {key: count / shots for key, count in measurement_counts.items()} @staticmethod def measurements_from_measurement_probabilities( @@ -206,11 +204,9 @@ def measurements_from_measurement_probabilities( Value is the probability the measurement occurred. """ measurements_list = [] - for bitstring in measurement_probabilities: + for bitstring, prob in measurement_probabilities.items(): measurement = list(bitstring) - individual_measurement_list = [measurement] * int( - round(measurement_probabilities[bitstring] * shots) - ) + individual_measurement_list = [measurement] * round(prob * shots) measurements_list.extend(individual_measurement_list) return np.asarray(measurements_list, dtype=int) @@ -254,8 +250,7 @@ def _from_object_internal(cls, result: GateModelTaskResult) -> GateModelQuantumT return GateModelQuantumTaskResult._from_object_internal_computational_basis_sampling( result ) - else: - return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) + return GateModelQuantumTaskResult._from_dict_internal_simulator_only(result) @classmethod def _from_object_internal_computational_basis_sampling( @@ -355,7 +350,7 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: elif type == "probability": result_type.value = np.array(result_type.value) elif type == "statevector": - result_type.value = np.array([complex(*value) for value in result_type.value]) + result_type.value = np.array(list(starmap(complex, result_type.value))) @staticmethod def _calculate_result_types( @@ -409,7 +404,7 @@ def _calculate_result_types( @staticmethod def _selected_measurements( - measurements: np.ndarray, measured_qubits: list[int], targets: Optional[list[int]] + measurements: np.ndarray, measured_qubits: list[int], targets: list[int] | None ) -> np.ndarray: if targets is not None and targets != measured_qubits: # Only some qubits targeted @@ -424,14 +419,13 @@ def _calculate_for_targets( measured_qubits: list[int], observable: Observable, targets: list[int], - ) -> Union[T, list[T]]: + ) -> T | list[T]: if targets: return calculate_function(measurements, measured_qubits, observable, targets) - else: - return [ - calculate_function(measurements, measured_qubits, observable, [i]) - for i in measured_qubits - ] + return [ + calculate_function(measurements, measured_qubits, observable, [i]) + for i in measured_qubits + ] @staticmethod def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: @@ -441,7 +435,7 @@ def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: @staticmethod def _probability_from_measurements( - measurements: np.ndarray, measured_qubits: list[int], targets: Optional[list[int]] + measurements: np.ndarray, measured_qubits: list[int], targets: list[int] | None ) -> np.ndarray: measurements = GateModelQuantumTaskResult._selected_measurements( measurements, measured_qubits, targets @@ -504,11 +498,10 @@ def _samples_from_measurements( def _result_type_hash(rt_type: dict) -> str: if hasattr(rt_type, "observable") and isinstance(rt_type.observable, list): rt_type.observable = GateModelQuantumTaskResult._replace_neg_zero(rt_type.observable) - return repr(dict(sorted(dict(rt_type).items(), key=lambda x: x[0]))) + return repr(dict(sorted(dict(rt_type).items(), key=operator.itemgetter(0)))) @staticmethod - def _replace_neg_zero(observable_matrix: Union[list, int]) -> Union[list, int]: + def _replace_neg_zero(observable_matrix: list | int) -> list | int: if isinstance(observable_matrix, list): return [GateModelQuantumTaskResult._replace_neg_zero(x) for x in observable_matrix] - else: - return 0 if observable_matrix == 0 else observable_matrix + return 0 if observable_matrix == 0 else observable_matrix diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 8417c71b2..285c347c8 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -10,9 +10,9 @@ # 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. +from __future__ import annotations import asyncio -from typing import Union from braket.tasks import ( AnnealingQuantumTaskResult, @@ -30,9 +30,9 @@ class LocalQuantumTask(QuantumTask): def __init__( self, - result: Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ], + result: GateModelQuantumTaskResult + | AnnealingQuantumTaskResult + | PhotonicModelQuantumTaskResult, ): self._id = result.task_metadata.id self._result = result @@ -60,9 +60,7 @@ def state(self) -> str: def result( self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: + ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: return self._result def async_result(self) -> asyncio.Task: diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index f07a1be7b..a0c2561c7 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -56,7 +56,7 @@ def result( Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: Get the quantum task result. Call async_result if you want the result in an asynchronous way. - """ # noqa E501 + """ # noqa: E501 @abstractmethod def async_result(self) -> asyncio.Task: @@ -66,7 +66,7 @@ def async_result(self) -> asyncio.Task: asyncio.Task: Get the quantum task result asynchronously. """ - def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: # noqa B027 + def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: # noqa: B027 """Get task metadata. Args: diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 67797d6c4..f01299155 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -19,7 +19,6 @@ from decimal import Decimal from enum import Enum from numbers import Number -from typing import Optional @dataclass @@ -269,15 +268,15 @@ def stitch( return new_time_series def discretize( - self, time_resolution: Optional[Decimal], value_resolution: Optional[Decimal] + self, time_resolution: Decimal | None, value_resolution: Decimal | None ) -> TimeSeries: """Creates a discretized version of the time series, rounding all times and values to the closest multiple of the corresponding resolution. Args: - time_resolution (Optional[Decimal]): Time resolution - value_resolution (Optional[Decimal]): Value resolution + time_resolution (Decimal | None): Time resolution + value_resolution (Decimal | None): Value resolution Returns: TimeSeries: A new discretized time series. diff --git a/src/braket/tracking/pricing.py b/src/braket/tracking/pricing.py index c269208c2..3bf379393 100644 --- a/src/braket/tracking/pricing.py +++ b/src/braket/tracking/pricing.py @@ -33,7 +33,7 @@ def get_prices(self) -> None: http = urllib3.PoolManager() price_url = os.environ.get( "BRAKET_PRICE_OFFERS_URL", - "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBraket/current/index.csv", # noqa: E501 + "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBraket/current/index.csv", ) response = http.request( "GET", @@ -42,7 +42,7 @@ def get_prices(self) -> None: ) response.auto_close = False - text_response = io.TextIOWrapper(response) + text_response = io.TextIOWrapper(response, encoding="utf-8") # Data starts on line 6 # @@ -53,7 +53,7 @@ def get_prices(self) -> None: text_response.readline() self._price_list = list(csv.DictReader(text_response)) - @lru_cache + @lru_cache # noqa: B019 def price_search(self, **kwargs: str) -> list[dict[str, str]]: """Searches the price list for a given set of parameters. diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 47c13625a..3b6214759 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -50,7 +50,7 @@ def start(self) -> Tracker: Returns: Tracker: self. """ - return self.__enter__() + return self.__enter__() # noqa: PLC2801 def stop(self) -> Tracker: """Stop tracking resources with this tracker. @@ -91,7 +91,7 @@ def qpu_tasks_cost(self) -> Decimal: total_cost = Decimal(0) for task_arn, details in self._resources.items(): if "qpu" in details["device"]: - total_cost = total_cost + _get_qpu_task_cost(task_arn, details) + total_cost += _get_qpu_task_cost(task_arn, details) return total_cost def simulator_tasks_cost(self) -> Decimal: @@ -114,7 +114,7 @@ def simulator_tasks_cost(self) -> Decimal: total_cost = Decimal(0) for task_arn, details in self._resources.items(): if "simulator" in details["device"]: - total_cost = total_cost + _get_simulator_task_cost(task_arn, details) + total_cost += _get_simulator_task_cost(task_arn, details) return total_cost def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: @@ -142,7 +142,7 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: 'billed_execution_duration' : datetime.timedelta(seconds=6, microseconds=123456)}} """ stats = {} - for _, details in self._resources.items(): + for details in self._resources.values(): device_stats = stats.get(details["device"], {}) shots = device_stats.get("shots", 0) + details["shots"] @@ -212,7 +212,7 @@ def _(self, event: _TaskCompletionEvent) -> None: def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: - if details["status"] in ["FAILED", "CANCELLED"] or details.get("has_reservation_arn"): + if details["status"] in {"FAILED", "CANCELLED"} or details.get("has_reservation_arn"): return Decimal(0) task_region = task_arn.split(":")[3] @@ -291,10 +291,8 @@ def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: if duration_price["Currency"] != "USD": raise ValueError(f"Expected USD, found {duration_price['Currency']}") - duration_cost = ( + return ( Decimal(duration_price["PricePerUnit"]) * Decimal(details["billed_duration"] / timedelta(milliseconds=1)) / Decimal(timedelta(**{duration_price["Unit"]: 1}) / timedelta(milliseconds=1)) ) - - return duration_cost diff --git a/src/braket/tracking/tracking_context.py b/src/braket/tracking/tracking_context.py index 37f4a3dc0..309383c1f 100644 --- a/src/braket/tracking/tracking_context.py +++ b/src/braket/tracking/tracking_context.py @@ -18,7 +18,7 @@ class TrackingContext: def __init__(self): self._trackers = set() - def register_tracker(self, tracker: Tracker) -> None: # noqa F821 + def register_tracker(self, tracker: Tracker) -> None: # noqa: F821 """Registers a tracker. Args: @@ -26,7 +26,7 @@ def register_tracker(self, tracker: Tracker) -> None: # noqa F821 """ self._trackers.add(tracker) - def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 + def deregister_tracker(self, tracker: Tracker) -> None: # noqa: F821 """Deregisters a tracker. Args: @@ -34,7 +34,7 @@ def deregister_tracker(self, tracker: Tracker) -> None: # noqa F821 """ self._trackers.remove(tracker) - def broadcast_event(self, event: _TrackingEvent) -> None: # noqa F821 + def broadcast_event(self, event: _TrackingEvent) -> None: # noqa: F821 """Broadcasts an event to all trackers. Args: diff --git a/src/braket/tracking/tracking_events.py b/src/braket/tracking/tracking_events.py index 793ff038c..689640632 100644 --- a/src/braket/tracking/tracking_events.py +++ b/src/braket/tracking/tracking_events.py @@ -14,7 +14,6 @@ from __future__ import annotations from dataclasses import dataclass -from typing import Optional @dataclass @@ -31,7 +30,7 @@ class _TaskCreationEvent(_TrackingEvent): @dataclass class _TaskCompletionEvent(_TrackingEvent): - execution_duration: Optional[float] + execution_duration: float | None status: str has_reservation_arn: bool = False diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 901cc819e..2227f31a3 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -335,14 +335,12 @@ def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: phi = 0.123 varphi = -0.543 matrix1 = np.array([[1, 2], [2, 4]]) - matrix2 = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) + matrix2 = np.array([ + [-6, 2 + 1j, -3, -5 + 2j], + [2 - 1j, 0, 2 - 1j, -5 + 4j], + [-3, 2 + 1j, 0, -4 + 3j], + [-5 - 2j, -5 - 4j, -4 - 3j, -6], + ]) obs = Observable.Hermitian(matrix1) @ Observable.Hermitian(matrix2) obs_targets = [0, 1, 2] circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) @@ -391,14 +389,12 @@ def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: dict[str theta = 0.432 phi = 0.123 varphi = -0.543 - array = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) + array = np.array([ + [-6, 2 + 1j, -3, -5 + 2j], + [2 - 1j, 0, 2 - 1j, -5 + 4j], + [-3, 2 + 1j, 0, -4 + 3j], + [-5 - 2j, -5 - 4j, -4 - 3j, -6], + ]) obs = Observable.Z() @ Observable.Hermitian(array) obs_targets = [0, 1, 2] circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) @@ -455,14 +451,12 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: dict[str theta = 0.432 phi = 0.123 varphi = -0.543 - array = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) + array = np.array([ + [-6, 2 + 1j, -3, -5 + 2j], + [2 - 1j, 0, 2 - 1j, -5 + 4j], + [-3, 2 + 1j, 0, -4 + 3j], + [-5 - 2j, -5 - 4j, -4 - 3j, -6], + ]) obs = Observable.Y() @ Observable.Hermitian(array) obs_targets = [0, 1, 2] circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) @@ -484,14 +478,12 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any] theta = 0.432 phi = 0.123 varphi = -0.543 - array = np.array( - [ - [-6, 2 + 1j, -3, -5 + 2j], - [2 - 1j, 0, 2 - 1j, -5 + 4j], - [-3, 2 + 1j, 0, -4 + 3j], - [-5 - 2j, -5 - 4j, -4 - 3j, -6], - ] - ) + array = np.array([ + [-6, 2 + 1j, -3, -5 + 2j], + [2 - 1j, 0, 2 - 1j, -5 + 4j], + [-3, 2 + 1j, 0, -4 + 3j], + [-5 - 2j, -5 - 4j, -4 - 3j, -6], + ]) obs1 = Observable.X() @ Observable.Y() obs1_targets = [0, 2] obs2 = Observable.Z() @ Observable.Z() diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 363c8344b..4899eb28c 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -16,134 +16,132 @@ def device(): @pytest.fixture def arbitrary_waveform(): - return ArbitraryWaveform( - [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.00017888439538396808, - 0.00046751103636033026, - 0.0011372942989106456, - 0.002577059611929697, - 0.005443941944632366, - 0.010731922770068104, - 0.01976701723583167, - 0.03406712171899736, - 0.05503285980691202, - 0.08350670755829034, - 0.11932853352131022, - 0.16107456696238298, - 0.20614055551722368, - 0.2512065440720643, - 0.292952577513137, - 0.328774403476157, - 0.3572482512275353, - 0.3782139893154499, - 0.3925140937986156, - 0.40154918826437913, - 0.4068371690898149, - 0.4097040514225177, - 0.41114381673553674, - 0.411813599998087, - 0.4121022266390633, - 0.4122174383870584, - 0.41226003881132406, - 0.4122746298554775, - 0.4122792591252675, - 0.4122806196003006, - 0.41228098995582513, - 0.41228108334474756, - 0.4122811051578895, - 0.4122811098772742, - 0.4122811108230642, - 0.4122811109986316, - 0.41228111102881937, - 0.41228111103362725, - 0.4122811110343365, - 0.41228111103443343, - 0.4122811110344457, - 0.4122811110344471, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.41228111103444737, - 0.4122811110344471, - 0.4122811110344457, - 0.41228111103443343, - 0.4122811110343365, - 0.41228111103362725, - 0.41228111102881937, - 0.4122811109986316, - 0.4122811108230642, - 0.4122811098772742, - 0.4122811051578895, - 0.41228108334474756, - 0.41228098995582513, - 0.4122806196003006, - 0.4122792591252675, - 0.4122746298554775, - 0.41226003881132406, - 0.4122174383870584, - 0.4121022266390633, - 0.411813599998087, - 0.41114381673553674, - 0.4097040514225176, - 0.4068371690898149, - 0.40154918826437913, - 0.3925140937986155, - 0.37821398931544986, - 0.3572482512275351, - 0.32877440347615655, - 0.2929525775131368, - 0.2512065440720641, - 0.20614055551722307, - 0.16107456696238268, - 0.11932853352131002, - 0.08350670755829034, - 0.05503285980691184, - 0.03406712171899729, - 0.01976701723583167, - 0.010731922770068058, - 0.005443941944632366, - 0.002577059611929697, - 0.0011372942989106229, - 0.00046751103636033026, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - ] - ) + return ArbitraryWaveform([ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.00017888439538396808, + 0.00046751103636033026, + 0.0011372942989106456, + 0.002577059611929697, + 0.005443941944632366, + 0.010731922770068104, + 0.01976701723583167, + 0.03406712171899736, + 0.05503285980691202, + 0.08350670755829034, + 0.11932853352131022, + 0.16107456696238298, + 0.20614055551722368, + 0.2512065440720643, + 0.292952577513137, + 0.328774403476157, + 0.3572482512275353, + 0.3782139893154499, + 0.3925140937986156, + 0.40154918826437913, + 0.4068371690898149, + 0.4097040514225177, + 0.41114381673553674, + 0.411813599998087, + 0.4121022266390633, + 0.4122174383870584, + 0.41226003881132406, + 0.4122746298554775, + 0.4122792591252675, + 0.4122806196003006, + 0.41228098995582513, + 0.41228108334474756, + 0.4122811051578895, + 0.4122811098772742, + 0.4122811108230642, + 0.4122811109986316, + 0.41228111102881937, + 0.41228111103362725, + 0.4122811110343365, + 0.41228111103443343, + 0.4122811110344457, + 0.4122811110344471, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.41228111103444737, + 0.4122811110344471, + 0.4122811110344457, + 0.41228111103443343, + 0.4122811110343365, + 0.41228111103362725, + 0.41228111102881937, + 0.4122811109986316, + 0.4122811108230642, + 0.4122811098772742, + 0.4122811051578895, + 0.41228108334474756, + 0.41228098995582513, + 0.4122806196003006, + 0.4122792591252675, + 0.4122746298554775, + 0.41226003881132406, + 0.4122174383870584, + 0.4121022266390633, + 0.411813599998087, + 0.41114381673553674, + 0.4097040514225176, + 0.4068371690898149, + 0.40154918826437913, + 0.3925140937986155, + 0.37821398931544986, + 0.3572482512275351, + 0.32877440347615655, + 0.2929525775131368, + 0.2512065440720641, + 0.20614055551722307, + 0.16107456696238268, + 0.11932853352131002, + 0.08350670755829034, + 0.05503285980691184, + 0.03406712171899729, + 0.01976701723583167, + 0.010731922770068058, + 0.005443941944632366, + 0.002577059611929697, + 0.0011372942989106229, + 0.00046751103636033026, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + ]) @pytest.fixture diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index 3af8554a1..df18a0db9 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -354,13 +354,11 @@ def test_discretize(register, driving_field, local_detuning): def test_converting_numpy_array_sites_to_ir(driving_field): hamiltonian = driving_field - sites = np.array( - [ - [0.0, 0.0], - [0.0, 1.0e-6], - [1e-6, 2.0e-6], - ] - ) + sites = np.array([ + [0.0, 0.0], + [0.0, 1.0e-6], + [1e-6, 2.0e-6], + ]) register = AtomArrangement() for site in sites: register.add(site) @@ -385,14 +383,12 @@ def test_site_validation_wrong_length(): @pytest.mark.xfail(raises=TypeError) def test_site_validation_non_number(): register = AtomArrangement() - register.add( + register.add([ + "not-a-number", [ - "not-a-number", - [ - "also-not-a-number", - ], - ] - ) + "also-not-a-number", + ], + ]) @pytest.mark.xfail(raises=TypeError) diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index 0e1fd4d48..cf3c7e0d9 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -36,156 +36,146 @@ class MockS3: "deviceId": "default", } - MOCK_S3_RESULT_GATE_MODEL = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1", + MOCK_S3_RESULT_GATE_MODEL = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "measuredQubits": [0, 1], + "taskMetadata": MOCK_TASK_METADATA, + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, + "instructions": [{"control": 0, "target": 1, "type": "cnot"}], }, - "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], - "measuredQubits": [0, 1], - "taskMetadata": MOCK_TASK_METADATA, - "additionalMetadata": { - "action": { - "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, - "instructions": [{"control": 0, "target": 1, "type": "cnot"}], - }, - }, - } - ) + }, + }) - MOCK_S3_RESULT_GATE_MODEL_WITH_RESULT_TYPES = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1", + MOCK_S3_RESULT_GATE_MODEL_WITH_RESULT_TYPES = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "measuredQubits": [0, 1], + "resultTypes": [ + { + "type": {"observable": ["h", "x"], "targets": [0, 1], "type": "expectation"}, + "value": 0.7071067811865474, }, - "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], - "measuredQubits": [0, 1], - "resultTypes": [ - { - "type": {"observable": ["h", "x"], "targets": [0, 1], "type": "expectation"}, - "value": 0.7071067811865474, - }, - { - "type": {"states": ["01", "10", "00", "11"], "type": "amplitude"}, - "value": { - "01": [0.0, 0.0], - "10": [0.0, 0.0], - "00": [0.7071067811865475, 0.0], - "11": [0.7071067811865475, 0.0], - }, - }, - ], - "taskMetadata": MOCK_TASK_METADATA, - "additionalMetadata": { - "action": { - "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, - "instructions": [{"control": 0, "target": 1, "type": "cnot"}], + { + "type": {"states": ["01", "10", "00", "11"], "type": "amplitude"}, + "value": { + "01": [0.0, 0.0], + "10": [0.0, 0.0], + "00": [0.7071067811865475, 0.0], + "11": [0.7071067811865475, 0.0], }, }, - } - ) - - MOCK_S3_RESULT_ANNEALING = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.annealing_task_result", - "version": "1", + ], + "taskMetadata": MOCK_TASK_METADATA, + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, + "instructions": [{"control": 0, "target": 1, "type": "cnot"}], }, - "solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "solutionCounts": [3, 2, 4], - "values": [0.0, 1.0, 2.0], - "variableCount": 4, - "taskMetadata": { - "id": "task_arn", - "shots": 100, - "deviceId": DWAVE_ARN, + }, + }) + + MOCK_S3_RESULT_ANNEALING = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.annealing_task_result", + "version": "1", + }, + "solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "solutionCounts": [3, 2, 4], + "values": [0.0, 1.0, 2.0], + "variableCount": 4, + "taskMetadata": { + "id": "task_arn", + "shots": 100, + "deviceId": DWAVE_ARN, + }, + "additionalMetadata": { + "action": { + "type": "ISING", + "linear": {"0": 0.3333, "1": -0.333, "4": -0.333, "5": 0.333}, + "quadratic": {"0,4": 0.667, "0,5": -1.0, "1,4": 0.667, "1,5": 0.667}, }, - "additionalMetadata": { - "action": { - "type": "ISING", - "linear": {"0": 0.3333, "1": -0.333, "4": -0.333, "5": 0.333}, - "quadratic": {"0,4": 0.667, "0,5": -1.0, "1,4": 0.667, "1,5": 0.667}, - }, - "dwaveMetadata": { - "activeVariables": [0], - "timing": { - "qpuSamplingTime": 100, - "qpuAnnealTimePerSample": 20, - "qpuAccessTime": 10917, - "qpuAccessOverheadTime": 3382, - "qpuReadoutTimePerSample": 274, - "qpuProgrammingTime": 9342, - "qpuDelayTimePerSample": 21, - "postProcessingOverheadTime": 117, - "totalPostProcessingTime": 117, - "totalRealTime": 10917, - "runTimeChip": 1575, - "annealTimePerRun": 20, - "readoutTimePerRun": 274, - }, + "dwaveMetadata": { + "activeVariables": [0], + "timing": { + "qpuSamplingTime": 100, + "qpuAnnealTimePerSample": 20, + "qpuAccessTime": 10917, + "qpuAccessOverheadTime": 3382, + "qpuReadoutTimePerSample": 274, + "qpuProgrammingTime": 9342, + "qpuDelayTimePerSample": 21, + "postProcessingOverheadTime": 117, + "totalPostProcessingTime": 117, + "totalRealTime": 10917, + "runTimeChip": 1575, + "annealTimePerRun": 20, + "readoutTimePerRun": 274, }, }, - } - ) + }, + }) - MOCK_S3_RESULT_PHOTONIC_MODEL = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.photonic_model_task_result", - "version": "1", + MOCK_S3_RESULT_PHOTONIC_MODEL = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.photonic_model_task_result", + "version": "1", + }, + "measurements": [[[1, 2, 3, 4]], [[4, 3, 2, 1]], [[0, 0, 0, 0]]], + "taskMetadata": { + "id": "task_arn", + "shots": 3, + "deviceId": XANADU_ARN, + }, + "additionalMetadata": { + "action": { + "source": "Vac | q[0]", }, - "measurements": [[[1, 2, 3, 4]], [[4, 3, 2, 1]], [[0, 0, 0, 0]]], - "taskMetadata": { - "id": "task_arn", - "shots": 3, - "deviceId": XANADU_ARN, + "xanaduMetadata": { + "compiledProgram": "DECLARE ro BIT[2];", }, - "additionalMetadata": { - "action": { - "source": "Vac | q[0]", - }, - "xanaduMetadata": { - "compiledProgram": "DECLARE ro BIT[2];", + }, + }) + + MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.analog_hamiltonian_simulation_task_result", + "version": "1", + }, + "taskMetadata": { + "id": "task_arn", + "shots": 3, + "deviceId": "mock_arn", + }, + "measurements": [ + { + "shotMetadata": {"shotStatus": "Success"}, + "shotResult": { + "preSequence": [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + "postSequence": [0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0], }, }, - } - ) - - MOCK_S3_RESULT_ANALOG_HAMILTONIAN_SIMULTION = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.analog_hamiltonian_simulation_task_result", - "version": "1", + { + "shotMetadata": {"shotStatus": "Partial Success"}, + "shotResult": { + "preSequence": [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], + "postSequence": None, + }, }, - "taskMetadata": { - "id": "task_arn", - "shots": 3, - "deviceId": "mock_arn", + { + "shotMetadata": {"shotStatus": "Failure"}, + "shotResult": {"preSequence": None, "postSequence": None}, }, - "measurements": [ - { - "shotMetadata": {"shotStatus": "Success"}, - "shotResult": { - "preSequence": [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], - "postSequence": [0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0], - }, - }, - { - "shotMetadata": {"shotStatus": "Partial Success"}, - "shotResult": { - "preSequence": [1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1], - "postSequence": None, - }, - }, - { - "shotMetadata": {"shotStatus": "Failure"}, - "shotResult": {"preSequence": None, "postSequence": None}, - }, - ], - } - ) + ], + }) def run_and_assert( @@ -352,19 +342,15 @@ def _create_task_args_and_kwargs( ] create_args += extra_args or [] create_kwargs = extra_kwargs or {} - create_kwargs.update( - { - "poll_timeout_seconds": ( - poll_timeout_seconds if poll_timeout_seconds is not None else default_poll_timeout - ), - "poll_interval_seconds": ( - poll_interval_seconds - if poll_interval_seconds is not None - else default_poll_interval - ), - "inputs": inputs, - "gate_definitions": gate_definitions, - "reservation_arn": reservation_arn, - } - ) + create_kwargs.update({ + "poll_timeout_seconds": ( + poll_timeout_seconds if poll_timeout_seconds is not None else default_poll_timeout + ), + "poll_interval_seconds": ( + poll_interval_seconds if poll_interval_seconds is not None else default_poll_interval + ), + "inputs": inputs, + "gate_definitions": gate_definitions, + "reservation_arn": reservation_arn, + }) return create_args, create_kwargs diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 3fed52aa2..fd1bcacde 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1902,7 +1902,6 @@ def test_get_devices_invalid_order_by(): @patch("braket.aws.aws_device.datetime") def test_get_device_availability(mock_utc_now): - class Expando: pass @@ -1913,13 +1912,11 @@ def __init__(self, status, *execution_window_args): self._properties.service = Expando() execution_windows = [ DeviceExecutionWindow.parse_raw( - json.dumps( - { - "executionDay": execution_day, - "windowStartHour": window_start_hour, - "windowEndHour": window_end_hour, - } - ) + json.dumps({ + "executionDay": execution_day, + "windowStartHour": window_start_hour, + "windowEndHour": window_end_hour, + }) ) for execution_day, window_start_hour, window_end_hour in execution_window_args ] @@ -2021,7 +2018,7 @@ def __init__(self, status, *execution_window_args): for test_set in test_sets: for test_item in test_set["test_items"]: test_date = test_item[0] - mock_utc_now.utcnow.return_value = test_date + mock_utc_now.now.return_value = test_date # flake8: noqa: C501 for i in range(len(test_item[1])): @@ -2034,9 +2031,9 @@ def __init__(self, status, *execution_window_args): ) expected = bool(test_item[1][i]) actual = device.is_available - assert ( - expected == actual - ), f"device_name: {device_name}, test_date: {test_date}, expected: {expected}, actual: {actual}" + assert expected == actual, ( + f"device_name: {device_name}, test_date: {test_date}, expected: {expected}, actual: {actual}" + ) @pytest.mark.parametrize( @@ -2102,57 +2099,53 @@ def test_parse_calibration_data(): @pytest.mark.parametrize( "bad_input", [ - ( - { - "gates": { - "0": { - "rx": [ - { - "name": "rx", - "qubits": ["0"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "incorrect_instr", - "arguments": [ - {"name": "qubit", "value": "0", "type": "string"} - ], - } - ], - } - ] - } - }, - "waveforms": {}, - } - ), - ( - { - "gates": { - "0": { - "rx": [ - { - "name": "cphaseshift", - "qubits": ["0"], - "arguments": ["-1.5707963267948966"], - "calibrations": [ - { - "name": "delay", - "arguments": [ - {"name": "bad_value", "value": "1", "type": "float"}, - {"name": "qubit", "value": None, "type": "string"}, - ], - } - ], - } - ] - } - }, - "waveforms": { - "blankId_waveform": {"waveformId": "blankId_waveform", "name": "bad_waveform"}, - }, - } - ), + ({ + "gates": { + "0": { + "rx": [ + { + "name": "rx", + "qubits": ["0"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "incorrect_instr", + "arguments": [ + {"name": "qubit", "value": "0", "type": "string"} + ], + } + ], + } + ] + } + }, + "waveforms": {}, + }), + ({ + "gates": { + "0": { + "rx": [ + { + "name": "cphaseshift", + "qubits": ["0"], + "arguments": ["-1.5707963267948966"], + "calibrations": [ + { + "name": "delay", + "arguments": [ + {"name": "bad_value", "value": "1", "type": "float"}, + {"name": "qubit", "value": None, "type": "string"}, + ], + } + ], + } + ] + } + }, + "waveforms": { + "blankId_waveform": {"waveformId": "blankId_waveform", "name": "bad_waveform"}, + }, + }), ], ) @pytest.mark.xfail(raises=ValueError) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 028ad3cc4..34e86691a 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -286,16 +286,14 @@ def result_setup(quantum_job_name): with open(file_path, "w") as write_file: write_file.write( - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"converged": True, "energy": -0.2}, - "dataFormat": "plaintext", - } - ) + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"converged": True, "energy": -0.2}, + "dataFormat": "plaintext", + }) ) with tarfile.open("model.tar.gz", "w:gz") as tar: @@ -737,16 +735,14 @@ def test_logs( quantum_job.logs(wait=True, poll_interval_seconds=0) captured = capsys.readouterr() - assert captured.out == "\n".join( - ( - "..", - "hi there #1", - "hi there #2", - "hi there #2a", - "hi there #3", - "", - ) - ) + assert captured.out == "\n".join(( + "..", + "hi there #1", + "hi there #2", + "hi there #2a", + "hi there #3", + "", + )) def test_logs_queue_progress( @@ -777,18 +773,16 @@ def test_logs_queue_progress( quantum_job.logs(wait=True, poll_interval_seconds=0) captured = capsys.readouterr() - assert captured.out == "\n".join( - ( - f"Job queue position: {queue_info['position']}", - "Running:", - "", - "hi there #1", - "hi there #2", - "hi there #2a", - "hi there #3", - "", - ) - ) + assert captured.out == "\n".join(( + f"Job queue position: {queue_info['position']}", + "Running:", + "", + "hi there #1", + "hi there #2", + "hi there #2a", + "hi there #3", + "", + )) @patch.dict("os.environ", {"JPY_PARENT_PID": "True"}) @@ -859,21 +853,19 @@ def get_log_events(log_group, log_stream, start_time, start_from_head, next_toke quantum_job.logs(wait=True, poll_interval_seconds=0) captured = capsys.readouterr() - assert captured.out == "\n".join( - ( - "..", - "\x1b[34mhi there #1\x1b[0m", - "\x1b[35mhi there #1\x1b[0m", - "\x1b[34mhi there #2\x1b[0m", - "\x1b[35mhi there #2\x1b[0m", - "\x1b[34mhi there #2a\x1b[0m", - "\x1b[35mhi there #2a\x1b[0m", - "\x1b[34mhi there #3\x1b[0m", - "\x1b[35mhi there #3\x1b[0m", - "\x1b[35mhi there #4\x1b[0m", - "", - ) - ) + assert captured.out == "\n".join(( + "..", + "\x1b[34mhi there #1\x1b[0m", + "\x1b[35mhi there #1\x1b[0m", + "\x1b[34mhi there #2\x1b[0m", + "\x1b[35mhi there #2\x1b[0m", + "\x1b[34mhi there #2a\x1b[0m", + "\x1b[35mhi there #2a\x1b[0m", + "\x1b[34mhi there #3\x1b[0m", + "\x1b[35mhi there #3\x1b[0m", + "\x1b[35mhi there #4\x1b[0m", + "", + )) def test_logs_error(quantum_job, generate_get_job_response, capsys): diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index cfe306127..c7ae7ed69 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -708,14 +708,12 @@ def test_create_task_with_reservation_arn(aws_session, arn, ahs_problem): def test_create_pulse_sequence(aws_session, arn, pulse_sequence): - expected_openqasm = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " set_frequency(predefined_frame_1, 6000000.0);", - "}", - ] - ) + expected_openqasm = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " set_frequency(predefined_frame_1, 6000000.0);", + "}", + ]) expected_program = OpenQASMProgram(source=expected_openqasm, inputs={}) aws_session.create_quantum_task.return_value = arn @@ -735,17 +733,15 @@ def test_create_pulse_gate_circuit( aws_session, arn, pulse_sequence, device_arn, device_parameters_class ): pulse_gate_circuit = Circuit().pulse_gate([0, 1], pulse_sequence, "my_PG") - expected_openqasm = "\n".join( - ( - "OPENQASM 3.0;", - "bit[2] b;", - "cal {", - " set_frequency(predefined_frame_1, 6000000.0);", - "}", - "b[0] = measure $0;", - "b[1] = measure $1;", - ) - ) + expected_openqasm = "\n".join(( + "OPENQASM 3.0;", + "bit[2] b;", + "cal {", + " set_frequency(predefined_frame_1, 6000000.0);", + "}", + "b[0] = measure $0;", + "b[1] = measure $1;", + )) expected_program = OpenQASMProgram(source=expected_openqasm, inputs={}) @@ -1054,29 +1050,27 @@ def test_create_circuit_with_shots_value_error(aws_session, arn, circuit): "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", ), ( - DwaveDeviceParameters.parse_obj( - { - "providerLevelParameters": { - "postprocessingType": "OPTIMIZATION", - "annealingOffsets": [3.67, 6.123], - "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], - "annealingDuration": 1, - "autoScale": False, - "beta": 0.2, - "chains": [[0, 1, 5], [6]], - "compensateFluxDrift": False, - "fluxBiases": [1.1, 2.2, 3.3, 4.4], - "initialState": [1, 3, 0, 1], - "maxResults": 1, - "programmingThermalizationDuration": 625, - "readoutThermalizationDuration": 256, - "reduceIntersampleCorrelation": False, - "reinitializeState": True, - "resultFormat": "RAW", - "spinReversalTransformCount": 100, - } + DwaveDeviceParameters.parse_obj({ + "providerLevelParameters": { + "postprocessingType": "OPTIMIZATION", + "annealingOffsets": [3.67, 6.123], + "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], + "annealingDuration": 1, + "autoScale": False, + "beta": 0.2, + "chains": [[0, 1, 5], [6]], + "compensateFluxDrift": False, + "fluxBiases": [1.1, 2.2, 3.3, 4.4], + "initialState": [1, 3, 0, 1], + "maxResults": 1, + "programmingThermalizationDuration": 625, + "readoutThermalizationDuration": 256, + "reduceIntersampleCorrelation": False, + "reinitializeState": True, + "resultFormat": "RAW", + "spinReversalTransformCount": 100, } - ), + }), "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", ), ( @@ -1131,29 +1125,27 @@ def test_create_circuit_with_shots_value_error(aws_session, arn, circuit): "arn:aws:braket:::device/qpu/d-wave/Advantage_system1", ), ( - Dwave2000QDeviceParameters.parse_obj( - { - "deviceLevelParameters": { - "postprocessingType": "OPTIMIZATION", - "annealingOffsets": [3.67, 6.123], - "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], - "annealingDuration": 1, - "autoScale": False, - "beta": 0.2, - "chains": [[0, 1, 5], [6]], - "compensateFluxDrift": False, - "fluxBiases": [1.1, 2.2, 3.3, 4.4], - "initialState": [1, 3, 0, 1], - "maxResults": 1, - "programmingThermalizationDuration": 625, - "readoutThermalizationDuration": 256, - "reduceIntersampleCorrelation": False, - "reinitializeState": True, - "resultFormat": "RAW", - "spinReversalTransformCount": 100, - } + Dwave2000QDeviceParameters.parse_obj({ + "deviceLevelParameters": { + "postprocessingType": "OPTIMIZATION", + "annealingOffsets": [3.67, 6.123], + "annealingSchedule": [[13.37, 10.08], [3.14, 1.618]], + "annealingDuration": 1, + "autoScale": False, + "beta": 0.2, + "chains": [[0, 1, 5], [6]], + "compensateFluxDrift": False, + "fluxBiases": [1.1, 2.2, 3.3, 4.4], + "initialState": [1, 3, 0, 1], + "maxResults": 1, + "programmingThermalizationDuration": 625, + "readoutThermalizationDuration": 256, + "reduceIntersampleCorrelation": False, + "reinitializeState": True, + "resultFormat": "RAW", + "spinReversalTransformCount": 100, } - ), + }), "arn:aws:braket:::device/qpu/d-wave/DW_2000Q_6", ), ( diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py index 2c748ecea..d5f2331d4 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task_batch.py @@ -41,8 +41,7 @@ def test_creation(mock_create): 1000, max_parallel=10, reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" ), ) assert batch.size == batch_size @@ -69,8 +68,7 @@ def test_successful(mock_create): 1000, max_parallel=10, reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" ), ) assert batch.size == batch_size @@ -96,8 +94,7 @@ def test_unsuccessful(mock_create): 1000, max_parallel=10, reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" ), ) assert not batch.unfinished @@ -134,8 +131,7 @@ def test_retry(mock_create): 1000, max_parallel=10, reservaion_arn=( - "arn:aws:braket:us-west-2:123456789123:" - "reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" + "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9" ), ) assert not batch.unfinished diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py index bf5974e41..c68ca7cf4 100644 --- a/test/unit_tests/braket/circuits/noise/test_noise_model.py +++ b/test/unit_tests/braket/circuits/noise/test_noise_model.py @@ -471,20 +471,20 @@ def test_remove_noise_at_invalid_index(): assert not "should not get here" -@pytest.mark.xfail(raises=ValueError) def test_add_invalid_noise(): noise_model = NoiseModel() - noise_model.add_noise(Mock(), Mock(spec=Criteria)) + with pytest.raises(TypeError): + noise_model.add_noise(Mock(), Mock(spec=Criteria)) -@pytest.mark.xfail(raises=ValueError) def test_add_invalid_criteria(): noise_model = NoiseModel() - noise_model.add_noise(Mock(spec=Noise), Mock()) + with pytest.raises(TypeError): + noise_model.add_noise(Mock(spec=Noise), Mock()) -@pytest.mark.xfail(raises=ValueError) def test_apply_to_circuit_list(): noise_model = NoiseModel() - noise_model.add_noise(Mock(), Mock(spec=Criteria)) - noise_model.apply([]) + with pytest.raises(TypeError): + noise_model.add_noise(Mock(), Mock(spec=Criteria)) + noise_model.apply([]) diff --git a/test/unit_tests/braket/circuits/test_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py index 166e7c8fc..0ce4eab38 100644 --- a/test/unit_tests/braket/circuits/test_basis_state.py +++ b/test/unit_tests/braket/circuits/test_basis_state.py @@ -98,11 +98,9 @@ def test_indexing(basis_state_input, index, substate_input): def test_bool(): - assert all( - [ - BasisState("100"), - BasisState("111"), - BasisState("1"), - ] - ) + assert all([ + BasisState("100"), + BasisState("111"), + BasisState("1"), + ]) assert not BasisState("0") diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 897828b27..a6ed047c6 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -178,13 +178,11 @@ def gate_calibrations(pulse_sequence, pulse_sequence_2): Gate.MS(FreeParameter("alpha"), FreeParameter("beta"), FreeParameter("gamma")), QubitSet([0, 1]), ) - return GateCalibrations( - { - calibration_key: pulse_sequence, - calibration_key_2: pulse_sequence, - calibration_key_3: pulse_sequence_2, - } - ) + return GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + calibration_key_3: pulse_sequence_2, + }) def test_repr_instructions(h): @@ -632,19 +630,17 @@ def test_measure_verbatim_box(): .add_instruction(Instruction(Measure(), 0)) ) expected_ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[2] q;", - "#pragma braket verbatim", - "box{", - "x q[0];", - "x q[1];", - "}", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[2] q;", + "#pragma braket verbatim", + "box{", + "x q[0];", + "x q[1];", + "}", + "b[0] = measure q[0];", + ]), inputs={}, ) assert circ == expected @@ -816,18 +812,16 @@ def test_measure_gate_after_measurement(): def test_to_ir_with_measure(): circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 2]) expected_ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[3] q;", - "h q[0];", - "cnot q[0], q[1];", - "cnot q[1], q[2];", - "b[0] = measure q[0];", - "b[1] = measure q[2];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ]), inputs={}, ) assert circ.to_ir("OPENQASM") == expected_ir @@ -835,18 +829,16 @@ def test_to_ir_with_measure(): def test_from_ir_with_measure(): ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[3] q;", - "h q[0];", - "cnot q[0], q[1];", - "cnot q[1], q[2];", - "b[0] = measure q[0];", - "b[1] = measure q[2];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ]), inputs={}, ) expected_circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(2) @@ -855,16 +847,14 @@ def test_from_ir_with_measure(): def test_from_ir_with_single_measure(): ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "h q[0];", - "cnot q[0], q[1];", - "b = measure q;", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b = measure q;", + ]), inputs={}, ) expected_circ = Circuit().h(0).cnot(0, 1).measure(0).measure(1) @@ -874,16 +864,14 @@ def test_from_ir_with_single_measure(): def test_from_ir_round_trip_transformation(): circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "h q[0];", - "cnot q[0], q[1];", - "b = measure q;", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b = measure q;", + ]), inputs={}, ) @@ -1077,19 +1065,17 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): .rx(2, 3 * FreeParameterExpression(1)), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[3] b;", - "qubit[3] q;", - "rx(0.15) q[0];", - "ry(0.3) q[1];", - "rx(3) q[2];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "rx(0.15) q[0];", + "ry(0.3) q[1];", + "rx(3) q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + ]), inputs={}, ), ), @@ -1112,29 +1098,27 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): Circuit().rx(0, 0.15).rx(1, 0.3), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.15) q[0];", - "rx(0.3) q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.15) q[0];", + "rx(0.3) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), @@ -1142,28 +1126,26 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): Circuit().rx(0, 0.15).rx(4, 0.3), OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.15) $0;", - "rx(0.3) $4;", - "b[0] = measure $0;", - "b[1] = measure $4;", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.15) $0;", + "rx(0.3) $4;", + "b[0] = measure $0;", + "b[1] = measure $4;", + ]), inputs={}, ), ), @@ -1174,29 +1156,27 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): .expectation(observable=Observable.I()), OpenQASMSerializationProperties(QubitReferenceType.PHYSICAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.15) $0;", - "#pragma braket verbatim", - "box{", - "rx(0.3) $4;", - "}", - "#pragma braket result expectation i all", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.15) $0;", + "#pragma braket verbatim", + "box{", + "rx(0.3) $4;", + "}", + "#pragma braket result expectation i all", + ]), inputs={}, ), ), @@ -1208,28 +1188,26 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): .expectation(observable=Observable.I(), target=0), None, OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[5] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.15) q[0];", - "rx(0.3) q[4];", - "#pragma braket noise bit_flip(0.2) q[3]", - "#pragma braket result expectation i(q[0])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.15) q[0];", + "rx(0.3) q[4];", + "#pragma braket noise bit_flip(0.2) q[3]", + "#pragma braket result expectation i(q[0])", + ]), inputs={}, ), ), @@ -1237,30 +1215,28 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): Circuit().rx(0, 0.15).rx(1, FreeParameter("theta")), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float theta;", - "bit[2] b;", - "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.15) q[0];", - "rx(theta) q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float theta;", + "bit[2] b;", + "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.15) q[0];", + "rx(theta) q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), @@ -1271,33 +1247,31 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): .cnot(target=0, control=[2, 3, 4]), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[5] b;", - "qubit[5] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.15) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "negctrl @ rx(0.15) q[2], q[0];", - "ctrl(2) @ rx(0.3) q[2], q[3], q[1];", - "ctrl(2) @ cnot q[2], q[3], q[4], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - "b[3] = measure q[3];", - "b[4] = measure q[4];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[5] b;", + "qubit[5] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.15) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "negctrl @ rx(0.15) q[2], q[0];", + "ctrl(2) @ rx(0.3) q[2], q[3], q[1];", + "ctrl(2) @ cnot q[2], q[3], q[4], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + "b[3] = measure q[3];", + "b[4] = measure q[4];", + ]), inputs={}, ), ), @@ -1305,31 +1279,29 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): Circuit().cnot(0, 1).cnot(target=2, control=3).cnot(target=4, control=[5, 6]), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[7] b;", - "qubit[7] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "cnot q[0], q[1];", - "cnot q[3], q[2];", - "ctrl @ cnot q[5], q[6], q[4];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - "b[3] = measure q[3];", - "b[4] = measure q[4];", - "b[5] = measure q[5];", - "b[6] = measure q[6];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[7] b;", + "qubit[7] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "cnot q[0], q[1];", + "cnot q[3], q[2];", + "ctrl @ cnot q[5], q[6], q[4];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + "b[3] = measure q[3];", + "b[4] = measure q[4];", + "b[5] = measure q[5];", + "b[6] = measure q[6];", + ]), inputs={}, ), ), @@ -1337,32 +1309,30 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): Circuit().h(0, power=-2.5).h(0, power=0).ms(0, 1, -0.1, -0.2, -0.3), OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian" - + "(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal ms(-0.1, -0.2, -0.3) $0, $1 {", - " shift_phase(predefined_frame_1, -0.1);", - " set_phase(predefined_frame_1, -0.3);", - " shift_phase(predefined_frame_1, -0.2);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "inv @ pow(2.5) @ h q[0];", - "pow(0) @ h q[0];", - "ms(-0.1, -0.2, -0.3) q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian" + + "(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal ms(-0.1, -0.2, -0.3) $0, $1 {", + " shift_phase(predefined_frame_1, -0.1);", + " set_phase(predefined_frame_1, -0.3);", + " shift_phase(predefined_frame_1, -0.2);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "inv @ pow(2.5) @ h q[0];", + "pow(0) @ h q[0];", + "ms(-0.1, -0.2, -0.3) q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), @@ -1401,25 +1371,22 @@ def test_circuit_to_ir_openqasm_with_gate_calibrations( Circuit().rx(0, 0.2), (Gate.Rx(FreeParameter("alpha")), QubitSet(0)), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float beta;", - "bit[1] b;", - "qubit[1] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian(3.0ms," - " 400.0ms, 0.2, 1, false);", - "}", - "defcal rx(0.2) $0 {", - " shift_phase(predefined_frame_1, 0.2);", - " shift_phase(predefined_frame_1, beta);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "rx(0.2) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float beta;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal rx(0.2) $0 {", + " shift_phase(predefined_frame_1, 0.2);", + " shift_phase(predefined_frame_1, beta);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "rx(0.2) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), @@ -1427,11 +1394,9 @@ def test_circuit_to_ir_openqasm_with_gate_calibrations( ) def test_circuit_with_parametric_defcal(circuit, calibration_key, expected_ir, pulse_sequence_3): serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) - gate_calibrations = GateCalibrations( - { - calibration_key: pulse_sequence_3, - } - ) + gate_calibrations = GateCalibrations({ + calibration_key: pulse_sequence_3, + }) assert ( circuit.to_ir( @@ -1448,37 +1413,33 @@ def test_parametric_circuit_with_fixed_argument_defcal(pulse_sequence): serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) calibration_key = (Gate.Z(), QubitSet([0, 1])) calibration_key_2 = (Gate.Rx(0.45), QubitSet([0])) - gate_calibrations = GateCalibrations( - { - calibration_key: pulse_sequence, - calibration_key_2: pulse_sequence, - } - ) + gate_calibrations = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + }) expected_ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal z $0, $1 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "defcal rx(0.45) $0 {", - " set_frequency(predefined_frame_1, 6000000.0);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "inv @ pow(2.5) @ h q[0];", - "pow(0) @ h q[0];", - "rx(theta) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float theta;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal z $0, $1 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "defcal rx(0.45) $0 {", + " set_frequency(predefined_frame_1, 6000000.0);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "inv @ pow(2.5) @ h q[0];", + "pow(0) @ h q[0];", + "rx(theta) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ) @@ -1499,9 +1460,9 @@ def test_circuit_with_partial_calibrations(pulse_sequence_2): circuit = Circuit().h(0, power=-2.5).h(0, power=0).ms(0, 1, -0.1, -0.2, -0.3) serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) gate_calibrations = ( - GateCalibrations( - {(Gate.MS(-0.1, FreeParameter("beta"), -0.3), QubitSet([0, 1])): pulse_sequence_2} - ), + GateCalibrations({ + (Gate.MS(-0.1, FreeParameter("beta"), -0.3), QubitSet([0, 1])): pulse_sequence_2 + }), ) circuit.to_ir( ir_type=IRType.OPENQASM, @@ -1545,33 +1506,30 @@ def foo( circ = Circuit().foo(0, -0.2) serialization_properties = OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL) - gate_calibrations = GateCalibrations( - { - (Foo(FreeParameter("beta")), QubitSet(0)): pulse_sequence_2( - **{"alpha": -0.1, "gamma": -0.3} - ) - } - ) + gate_calibrations = GateCalibrations({ + (Foo(FreeParameter("beta")), QubitSet(0)): pulse_sequence_2(**{ + "alpha": -0.1, + "gamma": -0.3, + }) + }) expected_ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "cal {", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - "}", - "defcal foo(-0.2) $0 {", - " shift_phase(predefined_frame_1, -0.1);", - " set_phase(predefined_frame_1, -0.3);", - " shift_phase(predefined_frame_1, -0.2);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "foo(-0.2) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "cal {", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "defcal foo(-0.2) $0 {", + " shift_phase(predefined_frame_1, -0.1);", + " set_phase(predefined_frame_1, -0.3);", + " shift_phase(predefined_frame_1, -0.2);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "foo(-0.2) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ) @@ -1591,335 +1549,291 @@ def foo( ( Circuit().h(0, control=1, control_state=0).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "negctrl @ h q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ h q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().cnot(target=0, control=1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "cnot q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "cnot q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().x(0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "negctrl @ x q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ x q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().rx(0, 0.15, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "ctrl @ rx(0.15) q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "ctrl @ rx(0.15) q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().ry(0, 0.2, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "ctrl @ ry(0.2) q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "ctrl @ ry(0.2) q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().rz(0, 0.25, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "negctrl @ rz(0.25) q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ rz(0.25) q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().s(target=0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "negctrl @ s q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ s q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().t(target=1, control=[0], control_state=[0]).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "negctrl @ t q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "negctrl @ t q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().cphaseshift(target=0, control=1, angle=0.15).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "cphaseshift(0.15) q[1], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "cphaseshift(0.15) q[1], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().ccnot(*[0, 1], target=2).measure(0).measure(1).measure(2), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[3] b;", - "qubit[3] q;", - "ccnot q[0], q[1], q[2];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "ccnot q[0], q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + ]), inputs={}, ), ), ( Circuit().h(0).state_vector(), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result state_vector", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + "#pragma braket result state_vector", + ]), inputs={}, ), ), ( Circuit().h(0).expectation(observables.X(), [0]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result expectation x(q[0])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + "#pragma braket result expectation x(q[0])", + ]), inputs={}, ), ), ( Circuit().h(0).expectation(observables.H() @ observables.X(), [0, 1]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "#pragma braket result expectation h(q[0]) @ x(q[1])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[2] q;", + "h q[0];", + "#pragma braket result expectation h(q[0]) @ x(q[1])", + ]), inputs={}, ), ), ( Circuit().h(0).variance(observables.H() @ observables.X(), [0, 1]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "#pragma braket result variance h(q[0]) @ x(q[1])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[2] q;", + "h q[0];", + "#pragma braket result variance h(q[0]) @ x(q[1])", + ]), inputs={}, ), ), ( Circuit().h(0).probability(target=[0]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result probability q[0]", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + "#pragma braket result probability q[0]", + ]), inputs={}, ), ), ( Circuit().bit_flip(0, 0.1).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise bit_flip(0.1) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise bit_flip(0.1) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().generalized_amplitude_damping(0, 0.1, 0.1).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise generalized_amplitude_damping(0.1, 0.1) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise generalized_amplitude_damping(0.1, 0.1) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().phase_flip(0, 0.2).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise phase_flip(0.2) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise phase_flip(0.2) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().depolarizing(0, 0.5).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise depolarizing(0.5) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise depolarizing(0.5) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().amplitude_damping(0, 0.8).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise amplitude_damping(0.8) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise amplitude_damping(0.8) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().phase_damping(0, 0.1).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise phase_damping(0.1) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise phase_damping(0.1) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().h(0).amplitude(state=["0", "1"]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - '#pragma braket result amplitude "0", "1"', - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + '#pragma braket result amplitude "0", "1"', + ]), inputs={}, ), ), @@ -1934,21 +1848,19 @@ def foo( .measure(3) .measure(4), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[5] b;", - "qubit[5] q;", - "negctrl @ rx(0.15) q[2], q[0];", - "ctrl(2) @ rx(0.3) q[2], q[3], q[1];", - "ctrl(2) @ cnot q[2], q[3], q[4], q[0];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - "b[3] = measure q[3];", - "b[4] = measure q[4];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[5] b;", + "qubit[5] q;", + "negctrl @ rx(0.15) q[2], q[0];", + "ctrl(2) @ rx(0.3) q[2], q[3], q[1];", + "ctrl(2) @ cnot q[2], q[3], q[4], q[0];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + "b[3] = measure q[3];", + "b[4] = measure q[4];", + ]), inputs={}, ), ), @@ -1965,160 +1877,140 @@ def foo( .measure(5) .measure(6), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[7] b;", - "qubit[7] q;", - "cnot q[0], q[1];", - "cnot q[3], q[2];", - "ctrl @ cnot q[5], q[6], q[4];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - "b[2] = measure q[2];", - "b[3] = measure q[3];", - "b[4] = measure q[4];", - "b[5] = measure q[5];", - "b[6] = measure q[6];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[7] b;", + "qubit[7] q;", + "cnot q[0], q[1];", + "cnot q[3], q[2];", + "ctrl @ cnot q[5], q[6], q[4];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + "b[3] = measure q[3];", + "b[4] = measure q[4];", + "b[5] = measure q[5];", + "b[6] = measure q[6];", + ]), inputs={}, ), ), ( Circuit().h(0, power=-2.5).h(0, power=0).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "inv @ pow(2.5) @ h q[0];", - "pow(0) @ h q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "inv @ pow(2.5) @ h q[0];", + "pow(0) @ h q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket unitary([[0, 1.0], [1.0, 0]]) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket unitary([[0, 1.0], [1.0, 0]]) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise pauli_channel(0.1, 0.2, 0.3) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().two_qubit_depolarizing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "#pragma braket noise two_qubit_depolarizing(0.1) q[0], q[1]", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "#pragma braket noise two_qubit_depolarizing(0.1) q[0], q[1]", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "#pragma braket noise two_qubit_dephasing(0.1) q[0], q[1]", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "#pragma braket noise two_qubit_dephasing(0.1) q[0], q[1]", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "#pragma braket noise two_qubit_dephasing(0.1) q[0], q[1]", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "#pragma braket noise two_qubit_dephasing(0.1) q[0], q[1]", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().h(0).sample(observable=Observable.Z(), target=0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result sample z(q[0])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + "#pragma braket result sample z(q[0])", + ]), inputs={}, ), ), ( Circuit().h(0).sample(observable=Observable.Z(), target=0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[1] q;", - "h q[0];", - "#pragma braket result sample z(q[0])", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[1] q;", + "h q[0];", + "#pragma braket result sample z(q[0])", + ]), inputs={}, ), ), ( Circuit().h(0).x(1).density_matrix(target=[0, 1]), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "qubit[2] q;", - "h q[0];", - "x q[1];", - "#pragma braket result density_matrix q[0], q[1]", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "qubit[2] q;", + "h q[0];", + "x q[1];", + "#pragma braket result density_matrix q[0], q[1]", + ]), inputs={}, ), ), @@ -2133,79 +2025,69 @@ def foo( ) .measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "#pragma braket noise " - "kraus([[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], " - "[0.31622777, 0]]) q[0]", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "#pragma braket noise " + "kraus([[0.9486833im, 0], [0, 0.9486833im]], [[0, 0.31622777], " + "[0.31622777, 0]]) q[0]", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().rx(0, FreeParameter("theta")).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float theta;", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().rx(0, np.pi).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "rx(π) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(π) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().rx(0, 2 * np.pi).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "rx(τ) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "rx(τ) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().gphase(0.15).x(0).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "gphase(0.15);", - "x q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "gphase(0.15);", + "x q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), @@ -2219,18 +2101,16 @@ def test_from_ir(expected_circuit, ir): def test_from_ir_inputs_updated(): circuit = Circuit().rx(0, 0.2).ry(0, 0.1).measure(0) openqasm = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float theta;", - "input float phi;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "ry(phi) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float theta;", + "input float phi;", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "ry(phi) q[0];", + "b[0] = measure q[0];", + ]), inputs={"theta": 0.2, "phi": 0.3}, ) assert Circuit.from_ir(source=openqasm, inputs={"phi": 0.1}) == circuit @@ -2242,113 +2122,101 @@ def test_from_ir_inputs_updated(): ( Circuit().h(0).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "gate my_gate a,b {", - "h a;", - "cnot a,b;", - "}", - "my_gate q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "gate my_gate a,b {", + "h a;", + "cnot a,b;", + "}", + "my_gate q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().h(0).h(1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "def my_sub(qubit q) {", - "h q;", - "}", - "h q[0];", - "my_sub(q[1]);", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "def my_sub(qubit q) {", + "h q;", + "}", + "h q[0];", + "my_sub(q[1]);", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "for uint i in [0:1] {", - "h q[i];", - "}", - "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "for uint i in [0:1] {", + "h q[i];", + "}", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "qubit[2] q;", - "for uint i in [0:1] {", - "h q[i];", - "}", - "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "for uint i in [0:1] {", + "h q[i];", + "}", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ]), inputs={}, ), ), ( Circuit().x(0).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "qubit[1] q;", - "bit c = 0;", - "if (c ==0){", - "x q[0];", - "}", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[1] q;", + "bit c = 0;", + "if (c ==0){", + "x q[0];", + "}", + "b[0] = measure q[0];", + ]), inputs={}, ), ), ( Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")).measure(0), OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "rx(2*theta) q[0];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "input float theta;", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "rx(2*theta) q[0];", + "b[0] = measure q[0];", + ]), inputs={}, ), ), @@ -3346,7 +3214,7 @@ def test_make_bound_circuit_bad_value(): theta = FreeParameter("theta") input_val = "invalid" circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - with pytest.raises(ValueError): + with pytest.raises(TypeError): circ.make_bound_circuit({"theta": input_val}) @@ -3429,33 +3297,31 @@ def test_pulse_circuit_to_openqasm(predefined_frame_1, user_defined_frame): serialization_properties=OpenQASMSerializationProperties( qubit_reference_type=QubitReferenceType.PHYSICAL ), - ).source == "\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "cal {", - " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1," " false);", - " waveform drag_gauss_wf_2 = drag_gaussian(3.0ms, 400.0ms, " "0.2, 1, false);", - "}", - "h $0;", - "cal {", - " set_frequency(predefined_frame_1, 3000000000.0);", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_1, drag_gauss_wf);", - "}", - "x $1;", - "cal {", - " set_frequency(predefined_frame_1, 3000000000.0);", - " play(user_defined_frame_0, gauss_wf);", - " play(predefined_frame_1, drag_gauss_wf_2);", - "}", - "h $1;", - "b[0] = measure $0;", - "b[1] = measure $1;", - ] - ) + ).source == "\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "cal {", + " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform drag_gauss_wf_2 = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + "}", + "h $0;", + "cal {", + " set_frequency(predefined_frame_1, 3000000000.0);", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf);", + "}", + "x $1;", + "cal {", + " set_frequency(predefined_frame_1, 3000000000.0);", + " play(user_defined_frame_0, gauss_wf);", + " play(predefined_frame_1, drag_gauss_wf_2);", + "}", + "h $1;", + "b[0] = measure $0;", + "b[1] = measure $1;", + ]) def test_pulse_circuit_conflicting_wf(predefined_frame_1, user_defined_frame): @@ -3543,24 +3409,22 @@ def test_parametrized_pulse_circuit(user_defined_frame): serialization_properties=OpenQASMSerializationProperties( qubit_reference_type=QubitReferenceType.PHYSICAL ), - ).source == "\n".join( - [ - "OPENQASM 3.0;", - "input float frequency;", - "bit[2] b;", - "cal {", - " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", - "}", - "rx(0.5) $0;", - "cal {", - " set_frequency(user_defined_frame_0, frequency);", - " play(user_defined_frame_0, gauss_wf);", - "}", - "b[0] = measure $0;", - "b[1] = measure $1;", - ] - ) + ).source == "\n".join([ + "OPENQASM 3.0;", + "input float frequency;", + "bit[2] b;", + "cal {", + " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", + " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", + "}", + "rx(0.5) $0;", + "cal {", + " set_frequency(user_defined_frame_0, frequency);", + " play(user_defined_frame_0, gauss_wf);", + "}", + "b[0] = measure $0;", + "b[1] = measure $1;", + ]) bound = bound_half(frequency=1e7) @@ -3569,23 +3433,21 @@ def test_parametrized_pulse_circuit(user_defined_frame): serialization_properties=OpenQASMSerializationProperties( qubit_reference_type=QubitReferenceType.PHYSICAL ), - ).source == "\n".join( - [ - "OPENQASM 3.0;", - "bit[2] b;", - "cal {", - " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", - "}", - "rx(0.5) $0;", - "cal {", - " set_frequency(user_defined_frame_0, 10000000.0);", - " play(user_defined_frame_0, gauss_wf);", - "}", - "b[0] = measure $0;", - "b[1] = measure $1;", - ] - ) + ).source == "\n".join([ + "OPENQASM 3.0;", + "bit[2] b;", + "cal {", + " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", + " waveform gauss_wf = gaussian(10.0us, 700.0ms, 1, false);", + "}", + "rx(0.5) $0;", + "cal {", + " set_frequency(user_defined_frame_0, 10000000.0);", + " play(user_defined_frame_0, gauss_wf);", + "}", + "b[0] = measure $0;", + "b[1] = measure $1;", + ]) def test_free_param_float_mix(): @@ -3601,15 +3463,13 @@ def test_circuit_with_global_phase(): serialization_properties=OpenQASMSerializationProperties( qubit_reference_type=QubitReferenceType.PHYSICAL ), - ).source == "\n".join( - [ - "OPENQASM 3.0;", - "bit[1] b;", - "gphase(0.15);", - "x $0;", - "b[0] = measure $0;", - ] - ) + ).source == "\n".join([ + "OPENQASM 3.0;", + "bit[1] b;", + "gphase(0.15);", + "x $0;", + "b[0] = measure $0;", + ]) def test_from_ir_round_trip_transformation_with_targeted_measurements(): @@ -3622,18 +3482,16 @@ def test_from_ir_round_trip_transformation_with_targeted_measurements(): .add_instruction(Instruction(Measure(index=0), 0)) ) ir = OpenQasmProgram( - source="\n".join( - [ - "OPENQASM 3.0;", - "bit[3] b;", - "qubit[3] q;", - "h q[0];", - "cnot q[0], q[1];", - "b[2] = measure q[1];", - "b[1] = measure q[2];", - "b[0] = measure q[0];", - ] - ), + source="\n".join([ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[2] = measure q[1];", + "b[1] = measure q[2];", + "b[0] = measure q[0];", + ]), inputs={}, ) diff --git a/test/unit_tests/braket/circuits/test_gate_calibration.py b/test/unit_tests/braket/circuits/test_gate_calibration.py index de3fa2fc6..13e7cfe8b 100644 --- a/test/unit_tests/braket/circuits/test_gate_calibration.py +++ b/test/unit_tests/braket/circuits/test_gate_calibration.py @@ -60,17 +60,16 @@ def test_filter(pulse_sequence): calibration_key = (Gate.Z(), QubitSet([0])) calibration_key_2 = (Gate.H(), QubitSet([1])) calibration_key_3 = (Gate.CZ(), QubitSet([0, 1])) - calibration = GateCalibrations( - { - calibration_key: pulse_sequence, - calibration_key_2: pulse_sequence, - calibration_key_3: pulse_sequence, - } - ) + calibration = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + calibration_key_3: pulse_sequence, + }) expected_calibration_1 = GateCalibrations({calibration_key: pulse_sequence}) - expected_calibration_2 = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_3: pulse_sequence} - ) + expected_calibration_2 = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_3: pulse_sequence, + }) expected_calibration_3 = GateCalibrations({calibration_key_2: pulse_sequence}) expected_calibration_4 = GateCalibrations({}) expected_calibration_5 = calibration @@ -86,15 +85,13 @@ def test_filter(pulse_sequence): def test_to_ir(pulse_sequence): calibration_key = (Gate.Rx(angle=1), QubitSet([0, 1])) calibration = GateCalibrations({calibration_key: pulse_sequence}) - expected_ir = "\n".join( - [ - "OPENQASM 3.0;", - "defcal rx(1.0) $0, $1 {", - " barrier test_frame_rf;", - " delay[1000s] test_frame_rf;", - "}", - ] - ) + expected_ir = "\n".join([ + "OPENQASM 3.0;", + "defcal rx(1.0) $0, $1 {", + " barrier test_frame_rf;", + " delay[1000s] test_frame_rf;", + "}", + ]) assert calibration.to_ir() == expected_ir @@ -103,45 +100,44 @@ def test_to_ir(pulse_sequence): def test_to_ir_with_bad_key(pulse_sequence): calibration_key = (Gate.Z(), QubitSet([0, 1])) calibration_key_2 = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} - ) - expected_ir = "\n".join( - [ - "OPENQASM 3.0;", - "defcal z $0, $1 {", - " barrier test_frame_rf;", - " delay[1000s] test_frame_rf;", - "}", - ] - ) + calibration = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + }) + expected_ir = "\n".join([ + "OPENQASM 3.0;", + "defcal z $0, $1 {", + " barrier test_frame_rf;", + " delay[1000s] test_frame_rf;", + "}", + ]) assert expected_ir == calibration.to_ir((Gate.Z(), QubitSet([1, 2]))) def test_to_ir_with_key(pulse_sequence): calibration_key = (Gate.Z(), QubitSet([0, 1])) calibration_key_2 = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} - ) - expected_ir = "\n".join( - [ - "OPENQASM 3.0;", - "defcal z $0, $1 {", - " barrier test_frame_rf;", - " delay[1000s] test_frame_rf;", - "}", - ] - ) + calibration = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + }) + expected_ir = "\n".join([ + "OPENQASM 3.0;", + "defcal z $0, $1 {", + " barrier test_frame_rf;", + " delay[1000s] test_frame_rf;", + "}", + ]) assert expected_ir == calibration.to_ir(calibration_key) def test_gate_calibrations_length(pulse_sequence): calibration_key = (Gate.Z(), QubitSet([0, 1])) calibration_key_2 = (Gate.H(), QubitSet([0, 1])) - calibration = GateCalibrations( - {calibration_key: pulse_sequence, calibration_key_2: pulse_sequence} - ) + calibration = GateCalibrations({ + calibration_key: pulse_sequence, + calibration_key_2: pulse_sequence, + }) assert len(calibration) == 2 diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 285b530c1..a72314ea3 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -1081,14 +1081,12 @@ def to_ir(pulse_gate): a_bound = gate.bind_values(a=a) a_bound_ir = to_ir(a_bound) - assert a_bound_ir == "\n".join( - [ - "cal {", - " set_frequency(user_frame, 3.0 + c);", - " delay[d * 1s] user_frame;", - "}", - ] - ) + assert a_bound_ir == "\n".join([ + "cal {", + " set_frequency(user_frame, 3.0 + c);", + " delay[d * 1s] user_frame;", + "}", + ]) assert a_bound_ir == to_ir( Gate.PulseGate(gate.pulse_sequence.make_bound_pulse_sequence({"a": a}), qubit_count) diff --git a/test/unit_tests/braket/circuits/test_measure.py b/test/unit_tests/braket/circuits/test_measure.py index 8911da5e4..c7df442a7 100644 --- a/test/unit_tests/braket/circuits/test_measure.py +++ b/test/unit_tests/braket/circuits/test_measure.py @@ -82,12 +82,10 @@ def test_measure_to_ir( Measure(), [1, 4], OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), - "\n".join( - [ - "b[0] = measure $1;", - "b[1] = measure $4;", - ] - ), + "\n".join([ + "b[0] = measure $1;", + "b[1] = measure $4;", + ]), ), ], ) diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index 3b7f08dd8..0774ca53e 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -646,12 +646,10 @@ def test_valid_values_pauli_channel_two_qubit(probs): "#pragma braket noise phase_damping(0.5) $3", ), ( - Noise.Kraus( - [ - np.eye(4) * np.sqrt(0.9), - np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1), - ] - ), + Noise.Kraus([ + np.eye(4) * np.sqrt(0.9), + np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1), + ]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 5], "#pragma braket noise kraus([" @@ -665,12 +663,10 @@ def test_valid_values_pauli_channel_two_qubit(probs): "[0, 0, 0.31622776601683794, 0]]) q[3], q[5]", ), ( - Noise.Kraus( - [ - np.eye(4) * np.sqrt(0.9), - np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1), - ] - ), + Noise.Kraus([ + np.eye(4) * np.sqrt(0.9), + np.kron([[1.0, 0.0], [0.0, 1.0]], [[0.0, 1.0], [1.0, 0.0]]) * np.sqrt(0.1), + ]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 5], "#pragma braket noise kraus([" @@ -684,12 +680,10 @@ def test_valid_values_pauli_channel_two_qubit(probs): "[0, 0, 0.31622776601683794, 0]]) $3, $5", ), ( - Noise.Kraus( - [ - np.array([[0.9486833j, 0], [0, 0.9486833j]]), - np.array([[0, 0.31622777], [0.31622777, 0]]), - ] - ), + Noise.Kraus([ + np.array([[0.9486833j, 0], [0, 0.9486833j]]), + np.array([[0, 0.31622777], [0.31622777, 0]]), + ]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "#pragma braket noise kraus([" @@ -697,12 +691,10 @@ def test_valid_values_pauli_channel_two_qubit(probs): "[0, 0.31622777], [0.31622777, 0]]) q[3]", ), ( - Noise.Kraus( - [ - np.array([[0.9486833j, 0], [0, 0.9486833j]]), - np.array([[0, 0.31622777], [0.31622777, 0]]), - ] - ), + Noise.Kraus([ + np.array([[0.9486833j, 0], [0, 0.9486833j]]), + np.array([[0, 0.31622777], [0.31622777, 0]]), + ]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "#pragma braket noise kraus([" diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index 38689398a..f592e1d19 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -155,7 +155,7 @@ def test_matmul_observable(): def test_matmul_non_observable(): - with pytest.raises(ValueError): + with pytest.raises(TypeError): Observable.I() @ "a" @@ -196,14 +196,14 @@ def test_observable_coeffs(observable): @pytest.mark.parametrize("parameter", ["foo", 1.2, -3]) def test_only_observables_sum_allowed(observable, parameter): add_observables_only = "Can only perform addition between observables." - with pytest.raises(ValueError, match=add_observables_only): + with pytest.raises(TypeError, match=add_observables_only): 2 * observable + parameter @pytest.mark.parametrize("parameter", ["foo", 1.2, -3]) def test_only_observables_subtraction_allowed(observable, parameter): add_observables_only = "Can only perform subtraction between observables." - with pytest.raises(ValueError, match=add_observables_only): + with pytest.raises(TypeError, match=add_observables_only): 2 * observable - parameter diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 213a29705..1ca2523c8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -374,9 +374,10 @@ def test_invalid_scalar_multiplication(expression, observable): [ ( (-3 * Observable.H()).to_matrix(), - np.array( - [[-2.12132034 + 0.0j, -2.12132034 + 0.0j], [-2.12132034 + 0.0j, 2.12132034 - 0.0j]] - ), + np.array([ + [-2.12132034 + 0.0j, -2.12132034 + 0.0j], + [-2.12132034 + 0.0j, 2.12132034 - 0.0j], + ]), ), ( (3 * Observable.Z()).to_matrix(), @@ -510,9 +511,12 @@ def test_flattened_tensor_product(): observable_one = Observable.Z() @ Observable.Y() observable_two = Observable.X() @ Observable.H() actual = Observable.TensorProduct([observable_one, observable_two]) - expected = Observable.TensorProduct( - [Observable.Z(), Observable.Y(), Observable.X(), Observable.H()] - ) + expected = Observable.TensorProduct([ + Observable.Z(), + Observable.Y(), + Observable.X(), + Observable.H(), + ]) assert expected == actual @@ -525,20 +529,19 @@ def test_flattened_tensor_product(): ), ( np.array([[0, -1j], [1j, 0]]), - np.array( - [[-0.70710678 + 0.0j, -0.70710678 + 0.0j], [0.0 + 0.70710678j, 0.0 - 0.70710678j]] - ) + np.array([ + [-0.70710678 + 0.0j, -0.70710678 + 0.0j], + [0.0 + 0.70710678j, 0.0 - 0.70710678j], + ]) .conj() .T, ), ( np.array([[1, 1 - 1j], [1 + 1j, -1]]), - np.array( - [ - [-0.45970084 - 0.0j, 0.62796303 - 0.62796303j], - [-0.88807383 - 0.0j, -0.32505758 + 0.32505758j], - ] - ), + np.array([ + [-0.45970084 - 0.0j, 0.62796303 - 0.62796303j], + [-0.88807383 - 0.0j, -0.32505758 + 0.32505758j], + ]), ), ], ) @@ -580,9 +583,10 @@ def test_tensor_product_to_ir(): def test_tensor_product_matmul_tensor(): t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - t2 = Observable.TensorProduct( - [Observable.Hermitian(matrix=Observable.I().to_matrix()), Observable.Y()] - ) + t2 = Observable.TensorProduct([ + Observable.Hermitian(matrix=Observable.I().to_matrix()), + Observable.Y(), + ]) t3 = t1 @ t2 assert t3.to_ir() == ["z", "i", "x", [[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]], "y"] assert t3.qubit_count == 5 @@ -605,7 +609,7 @@ def test_tensor_product_eigenvalue_index_out_of_bounds(): def test_tensor_product_value_error(): - with pytest.raises(ValueError): + with pytest.raises(TypeError): Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 6c2976812..39ad1c299 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -42,73 +42,67 @@ AnalogHamiltonianSimulationQuantumTaskResult, ) -GATE_MODEL_RESULT = GateModelTaskResult( - **{ - "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], - "measuredQubits": [0, 1], - "taskMetadata": { - "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, - "id": "task_arn", - "shots": 100, - "deviceId": "default", +GATE_MODEL_RESULT = GateModelTaskResult(**{ + "measurements": [[0, 0], [0, 0], [0, 0], [1, 1]], + "measuredQubits": [0, 1], + "taskMetadata": { + "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, + "id": "task_arn", + "shots": 100, + "deviceId": "default", + }, + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, + "instructions": [{"control": 0, "target": 1, "type": "cnot"}], }, - "additionalMetadata": { - "action": { - "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, - "instructions": [{"control": 0, "target": 1, "type": "cnot"}], - }, - }, - } -) - -ANNEALING_RESULT = AnnealingTaskResult( - **{ - "solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], - "solutionCounts": [3, 2, 4], - "values": [0.0, 1.0, 2.0], - "variableCount": 4, - "taskMetadata": { - "id": "task_arn", - "shots": 100, - "deviceId": "device_id", + }, +}) + +ANNEALING_RESULT = AnnealingTaskResult(**{ + "solutions": [[-1, -1, -1, -1], [1, -1, 1, 1], [1, -1, -1, 1]], + "solutionCounts": [3, 2, 4], + "values": [0.0, 1.0, 2.0], + "variableCount": 4, + "taskMetadata": { + "id": "task_arn", + "shots": 100, + "deviceId": "device_id", + }, + "additionalMetadata": { + "action": { + "type": "ISING", + "linear": {"0": 0.3333, "1": -0.333, "4": -0.333, "5": 0.333}, + "quadratic": {"0,4": 0.667, "0,5": -1.0, "1,4": 0.667, "1,5": 0.667}, }, - "additionalMetadata": { - "action": { - "type": "ISING", - "linear": {"0": 0.3333, "1": -0.333, "4": -0.333, "5": 0.333}, - "quadratic": {"0,4": 0.667, "0,5": -1.0, "1,4": 0.667, "1,5": 0.667}, - }, - "dwaveMetadata": { - "activeVariables": [0], - "timing": { - "qpuSamplingTime": 100, - "qpuAnnealTimePerSample": 20, - "qpuAccessTime": 10917, - "qpuAccessOverheadTime": 3382, - "qpuReadoutTimePerSample": 274, - "qpuProgrammingTime": 9342, - "qpuDelayTimePerSample": 21, - "postProcessingOverheadTime": 117, - "totalPostProcessingTime": 117, - "totalRealTime": 10917, - "runTimeChip": 1575, - "annealTimePerRun": 20, - "readoutTimePerRun": 274, - }, + "dwaveMetadata": { + "activeVariables": [0], + "timing": { + "qpuSamplingTime": 100, + "qpuAnnealTimePerSample": 20, + "qpuAccessTime": 10917, + "qpuAccessOverheadTime": 3382, + "qpuReadoutTimePerSample": 274, + "qpuProgrammingTime": 9342, + "qpuDelayTimePerSample": 21, + "postProcessingOverheadTime": 117, + "totalPostProcessingTime": 117, + "totalRealTime": 10917, + "runTimeChip": 1575, + "annealTimePerRun": 20, + "readoutTimePerRun": 274, }, }, - } -) + }, +}) -AHS_RESULT = AnalogHamiltonianSimulationTaskResult( - **{ - "taskMetadata": { - "id": "rydberg", - "shots": 100, - "deviceId": "rydbergLocalSimulator", - }, - } -) +AHS_RESULT = AnalogHamiltonianSimulationTaskResult(**{ + "taskMetadata": { + "id": "rydberg", + "shots": 100, + "deviceId": "rydbergLocalSimulator", + }, +}) class DummyCircuitSimulator(BraketSimulator): @@ -127,31 +121,29 @@ def run( @property def properties(self) -> DeviceCapabilities: - return DeviceCapabilities.parse_obj( - { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], + return DeviceCapabilities.parse_obj({ + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], }, - "action": { - "braket.ir.openqasm.program": { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], - }, - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - }, + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], }, - "deviceParameters": {}, - } - ) + }, + "deviceParameters": {}, + }) class DummyJaqcdSimulator(BraketSimulator): @@ -171,27 +163,25 @@ def run( @property def properties(self) -> DeviceCapabilities: - return DeviceCapabilities.parse_obj( - { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "action": { - "braket.ir.jaqcd.program": { - "actionType": "braket.ir.jaqcd.program", - "version": ["1"], - }, + return DeviceCapabilities.parse_obj({ + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.jaqcd.program": { + "actionType": "braket.ir.jaqcd.program", + "version": ["1"], }, - "deviceParameters": {}, - } - ) + }, + "deviceParameters": {}, + }) def assert_shots(self, shots): assert self._shots == shots @@ -211,46 +201,42 @@ def run( @property def properties(self) -> DeviceCapabilities: - device_properties = DeviceCapabilities.parse_obj( - { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "00:00", - "windowEndHour": "23:59:59", - } - ], - "shotsRange": [1, 10], - }, - "action": { - "braket.ir.openqasm.program": { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], + device_properties = DeviceCapabilities.parse_obj({ + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "00:00", + "windowEndHour": "23:59:59", } - }, - "deviceParameters": {}, - } - ) - oq3_action = OpenQASMDeviceActionProperties.parse_raw( - json.dumps( - { + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { "actionType": "braket.ir.openqasm.program", "version": ["1"], - "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], - "supportedResultTypes": [ - {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, - ], - "supportedPragmas": [ - "braket_unitary_matrix", - "braket_result_type_sample", - "braket_result_type_expectation", - "braket_result_type_variance", - "braket_result_type_probability", - "braket_result_type_state_vector", - ], } - ) + }, + "deviceParameters": {}, + }) + oq3_action = OpenQASMDeviceActionProperties.parse_raw( + json.dumps({ + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], + "supportedResultTypes": [ + {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, + ], + "supportedPragmas": [ + "braket_unitary_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_state_vector", + ], + }) ) device_properties.action[DeviceActionType.OPENQASM] = oq3_action return device_properties @@ -283,51 +269,47 @@ def run( @property def properties(self) -> DeviceCapabilities: - device_properties = DeviceCapabilities.parse_obj( - { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "action": {}, - "deviceParameters": {}, - } - ) + device_properties = DeviceCapabilities.parse_obj({ + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": {}, + "deviceParameters": {}, + }) oq3_action = OpenQASMDeviceActionProperties.parse_raw( - json.dumps( - { - "actionType": "braket.ir.openqasm.program", - "version": ["1"], - "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], - "supportedResultTypes": [ - {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, - ], - "supportedPragmas": [ - "braket_noise_bit_flip", - "braket_noise_depolarizing", - "braket_noise_kraus", - "braket_noise_pauli_channel", - "braket_noise_generalized_amplitude_damping", - "braket_noise_amplitude_damping", - "braket_noise_phase_flip", - "braket_noise_phase_damping", - "braket_noise_two_qubit_dephasing", - "braket_noise_two_qubit_depolarizing", - "braket_unitary_matrix", - "braket_result_type_sample", - "braket_result_type_expectation", - "braket_result_type_variance", - "braket_result_type_probability", - "braket_result_type_density_matrix", - ], - } - ) + json.dumps({ + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["rx", "ry", "h", "cy", "cnot", "unitary"], + "supportedResultTypes": [ + {"name": "StateVector", "observables": None, "minShots": 0, "maxShots": 0}, + ], + "supportedPragmas": [ + "braket_noise_bit_flip", + "braket_noise_depolarizing", + "braket_noise_kraus", + "braket_noise_pauli_channel", + "braket_noise_generalized_amplitude_damping", + "braket_noise_amplitude_damping", + "braket_noise_phase_flip", + "braket_noise_phase_damping", + "braket_noise_two_qubit_dephasing", + "braket_noise_two_qubit_depolarizing", + "braket_unitary_matrix", + "braket_result_type_sample", + "braket_result_type_expectation", + "braket_result_type_variance", + "braket_result_type_probability", + "braket_result_type_density_matrix", + ], + }) ) device_properties.action[DeviceActionType.OPENQASM] = oq3_action return device_properties @@ -339,27 +321,25 @@ def run(self, problem: ir.annealing.Problem, *args, **kwargs) -> AnnealingTaskRe @property def properties(self) -> DeviceCapabilities: - return DeviceCapabilities.parse_obj( - { - "service": { - "executionWindows": [ - { - "executionDay": "Everyday", - "windowStartHour": "11:00", - "windowEndHour": "12:00", - } - ], - "shotsRange": [1, 10], - }, - "action": { - "braket.ir.annealing.problem": { - "actionType": "braket.ir.annealing.problem", - "version": ["1"], + return DeviceCapabilities.parse_obj({ + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", } - }, - "deviceParameters": {}, - } - ) + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.annealing.problem": { + "actionType": "braket.ir.annealing.problem", + "version": ["1"], + } + }, + "deviceParameters": {}, + }) class DummyRydbergSimulator(BraketSimulator): @@ -513,16 +493,14 @@ def test_run_gate_model_inputs(): task = sim.run(circuit, inputs={"theta": 2}, shots=10) dummy.run.assert_called_with( Program( - source="\n".join( - ( - "OPENQASM 3.0;", - "input float theta;", - "bit[1] b;", - "qubit[1] q;", - "rx(theta) q[0];", - "b[0] = measure q[0];", - ) - ), + source="\n".join(( + "OPENQASM 3.0;", + "input float theta;", + "bit[1] b;", + "qubit[1] q;", + "rx(theta) q[0];", + "b[0] = measure q[0];", + )), inputs={"theta": 2}, ), shots=10, @@ -758,8 +736,7 @@ def test_run_noisy_circuit_with_noise_model(mock_run, noise_model): _ = device.run(circuit, shots=4) expected_warning = ( - "The noise model of the device is applied to a circuit that already has noise " - "instructions." + "The noise model of the device is applied to a circuit that already has noise instructions." ) expected_circuit = textwrap.dedent( """ diff --git a/test/unit_tests/braket/jobs/local/test_local_job.py b/test/unit_tests/braket/jobs/local/test_local_job.py index 185b059e3..07f42a323 100644 --- a/test/unit_tests/braket/jobs/local/test_local_job.py +++ b/test/unit_tests/braket/jobs/local/test_local_job.py @@ -49,32 +49,24 @@ def test_envs(): @pytest.mark.parametrize( "creation_kwargs", [ - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - "checkpointConfig": {"localPath": "test/local/path/"}, - } - ), - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - "checkpointConfig": {}, - } - ), - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, - } - ), - ( - { - "jobName": "Test-Job-Name", - "algorithmSpecification": {}, - } - ), + ({ + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + "checkpointConfig": {"localPath": "test/local/path/"}, + }), + ({ + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + "checkpointConfig": {}, + }), + ({ + "jobName": "Test-Job-Name", + "algorithmSpecification": {"containerImage": {"uri": "file://test-URI"}}, + }), + ({ + "jobName": "Test-Job-Name", + "algorithmSpecification": {}, + }), ], ) @patch("braket.jobs.local.local_job.prepare_quantum_job") diff --git a/test/unit_tests/braket/jobs/local/test_local_job_container.py b/test/unit_tests/braket/jobs/local/test_local_job_container.py index 47efceef5..4d2827516 100644 --- a/test/unit_tests/braket/jobs/local/test_local_job_container.py +++ b/test/unit_tests/braket/jobs/local/test_local_job_container.py @@ -61,9 +61,16 @@ def test_start_and_stop(mock_run, mock_check_output, image_uri, aws_session): with _LocalJobContainer(image_uri, aws_session): pass mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) assert mock_check_output.call_count == 2 mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 1 @@ -124,9 +131,16 @@ def test_pull_container( ): pass mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) assert mock_check_output.call_count == len(check_output) mock_run.assert_any_call(["docker", "login", "-u", "AWS", "-p", test_token, repo_uri]) mock_run.assert_any_call(["docker", "pull", image_uri]) @@ -152,9 +166,16 @@ def test_pull_container_forced_update_invalid_name( pass mock_logger.warning.assert_called_with(f"Unable to update {local_image_name}.") mock_check_output.assert_any_call(["docker", "images", "-q", local_image_name]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) assert mock_check_output.call_count == 2 mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 1 @@ -183,12 +204,23 @@ def test_run_job_success( container.run_local_job(env_variables) assert container.run_log == "this\nis a\ntest\n" mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) - mock_check_output.assert_any_call( - ["docker", "exec", running_container_name, "printenv", "SAGEMAKER_PROGRAM"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) + mock_check_output.assert_any_call([ + "docker", + "exec", + running_container_name, + "printenv", + "SAGEMAKER_PROGRAM", + ]) assert mock_check_output.call_count == 3 mock_popen.assert_called_with( [ @@ -234,12 +266,23 @@ def test_run_customer_script_fails( container.run_local_job(env_variables) assert container.run_log == "this\nis a\ntest\nProcess exited with code: 400" mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) - mock_check_output.assert_any_call( - ["docker", "exec", running_container_name, "printenv", "SAGEMAKER_PROGRAM"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) + mock_check_output.assert_any_call([ + "docker", + "exec", + running_container_name, + "printenv", + "SAGEMAKER_PROGRAM", + ]) assert mock_check_output.call_count == 3 mock_popen.assert_called_with( [ @@ -287,7 +330,7 @@ def test_running_throws_exception( assert container.run_log == expected_exception assert mock_check_output.call_count == 3 mock_run.assert_called_with(["docker", "stop", running_container_name]) - mock_logger.error.assert_called_with(expected_exception) + mock_logger.exception.assert_called_with(expected_exception) @patch("subprocess.check_output") @@ -304,12 +347,24 @@ def test_make_dir(mock_run, mock_check_output, repo_uri, image_uri, aws_session) with _LocalJobContainer(image_uri, aws_session) as container: container.makedir(test_dir_path) mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) - mock_check_output.assert_any_call( - ["docker", "exec", running_container_name, "mkdir", "-p", test_dir_path] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) + mock_check_output.assert_any_call([ + "docker", + "exec", + running_container_name, + "mkdir", + "-p", + test_dir_path, + ]) assert mock_check_output.call_count == 3 mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 1 @@ -331,22 +386,30 @@ def test_copy_to(mock_run, mock_check_output, repo_uri, image_uri, aws_session): with _LocalJobContainer(image_uri, aws_session) as container: container.copy_to(source_path, dest_path) mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) - mock_check_output.assert_any_call( - [ - "docker", - "exec", - running_container_name, - "mkdir", - "-p", - str(PurePosixPath("test", "dest", "dir", "path")), - ] - ) - mock_check_output.assert_any_call( - ["docker", "cp", source_path, f"{running_container_name}:{dest_path}"] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) + mock_check_output.assert_any_call([ + "docker", + "exec", + running_container_name, + "mkdir", + "-p", + str(PurePosixPath("test", "dest", "dir", "path")), + ]) + mock_check_output.assert_any_call([ + "docker", + "cp", + source_path, + f"{running_container_name}:{dest_path}", + ]) assert mock_check_output.call_count == 4 mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 1 @@ -368,12 +431,22 @@ def test_copy_from(mock_run, mock_check_output, repo_uri, image_uri, aws_session with _LocalJobContainer(image_uri, aws_session) as container: container.copy_from(source_path, dest_path) mock_check_output.assert_any_call(["docker", "images", "-q", image_uri]) - mock_check_output.assert_any_call( - ["docker", "run", "-d", "--rm", local_image_name, "tail", "-f", "/dev/null"] - ) - mock_check_output.assert_any_call( - ["docker", "cp", f"{running_container_name}:{source_path}", dest_path] - ) + mock_check_output.assert_any_call([ + "docker", + "run", + "-d", + "--rm", + local_image_name, + "tail", + "-f", + "/dev/null", + ]) + mock_check_output.assert_any_call([ + "docker", + "cp", + f"{running_container_name}:{source_path}", + dest_path, + ]) assert mock_check_output.call_count == 3 mock_run.assert_any_call(["docker", "stop", running_container_name]) assert mock_run.call_count == 1 diff --git a/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py b/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py index 51199055f..07ca52f79 100644 --- a/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py +++ b/test/unit_tests/braket/jobs/local/test_local_job_container_setup.py @@ -188,13 +188,11 @@ def test_input(container, aws_session, creation_kwargs, input_data_config): def test_duplicate_input(container, aws_session, creation_kwargs, input_data_config): - input_data_config.append( - { - # this is a duplicate channel - "channelName": "single-file", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/irrelevant"}}, - } - ) + input_data_config.append({ + # this is a duplicate channel + "channelName": "single-file", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/irrelevant"}}, + }) creation_kwargs.update({"inputDataConfig": input_data_config}) dupes_not_allowed = "Duplicate channel names not allowed for input data: single-file" with pytest.raises(ValueError, match=dupes_not_allowed): @@ -202,13 +200,11 @@ def test_duplicate_input(container, aws_session, creation_kwargs, input_data_con def test_no_data_input(container, aws_session, creation_kwargs, input_data_config): - input_data_config.append( - { - # this channel won't match any data - "channelName": "no-data", - "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/irrelevant"}}, - } - ) + input_data_config.append({ + # this channel won't match any data + "channelName": "no-data", + "dataSource": {"s3DataSource": {"s3Uri": "s3://input_bucket/irrelevant"}}, + }) creation_kwargs.update({"inputDataConfig": input_data_config}) no_data_found = "No data found for channel 'no-data'" with pytest.raises(RuntimeError, match=no_data_found): diff --git a/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py index a4ac78f26..bf40bdfd4 100644 --- a/test/unit_tests/braket/jobs/test_data_persistence.py +++ b/test/unit_tests/braket/jobs/test_data_persistence.py @@ -37,35 +37,31 @@ "", PersistedJobDataFormat.PLAINTEXT, {"converged": True, "energy": -0.2}, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"converged": True, "energy": -0.2}, - "dataFormat": "plaintext", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"converged": True, "energy": -0.2}, + "dataFormat": "plaintext", + }), ), ( "job_pickled_simple_dict", "suffix1", PersistedJobDataFormat.PICKLED_V4, {"converged": True, "energy": -0.2}, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": { - "converged": "gASILg==\n", - "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", - }, - "dataFormat": "pickled_v4", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": { + "converged": "gASILg==\n", + "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", + }, + "dataFormat": "pickled_v4", + }), ), ], ) @@ -105,35 +101,31 @@ def test_save_job_checkpoint_raises_error_empty_data(checkpoint_data): "job_plaintext_simple_dict", "", PersistedJobDataFormat.PLAINTEXT, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"converged": True, "energy": -0.2}, - "dataFormat": "plaintext", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"converged": True, "energy": -0.2}, + "dataFormat": "plaintext", + }), {"converged": True, "energy": -0.2}, ), ( "job_pickled_simple_dict", "", PersistedJobDataFormat.PICKLED_V4, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": { - "converged": "gASILg==\n", - "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", - }, - "dataFormat": "pickled_v4", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": { + "converged": "gASILg==\n", + "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", + }, + "dataFormat": "pickled_v4", + }), {"converged": True, "energy": -0.2}, ), ], @@ -180,19 +172,17 @@ def test_load_job_checkpoint_raises_error_corrupted_data(): file_path = f"{tmp_dir}/{job_name}_{file_suffix}.json" with open(file_path, "w") as corrupted_file: corrupted_file.write( - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": { - "converged": "gASILg==\n", - "energy": "gASVCgBHv--corrupted---\n", - }, - "dataFormat": "pickled_v4", - } - ) + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": { + "converged": "gASILg==\n", + "energy": "gASVCgBHv--corrupted---\n", + }, + "dataFormat": "pickled_v4", + }) ) with patch.dict( @@ -231,33 +221,29 @@ def test_save_and_load_job_checkpoint(): ( PersistedJobDataFormat.PLAINTEXT, {"converged": True, "energy": -0.2}, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": {"converged": True, "energy": -0.2}, - "dataFormat": "plaintext", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": {"converged": True, "energy": -0.2}, + "dataFormat": "plaintext", + }), ), ( PersistedJobDataFormat.PICKLED_V4, {"converged": True, "energy": -0.2}, - json.dumps( - { - "braketSchemaHeader": { - "name": "braket.jobs_data.persisted_job_data", - "version": "1", - }, - "dataDictionary": { - "converged": "gASILg==\n", - "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", - }, - "dataFormat": "pickled_v4", - } - ), + json.dumps({ + "braketSchemaHeader": { + "name": "braket.jobs_data.persisted_job_data", + "version": "1", + }, + "dataDictionary": { + "converged": "gASILg==\n", + "energy": "gASVCgAAAAAAAABHv8mZmZmZmZou\n", + }, + "dataFormat": "pickled_v4", + }), ), ], ) diff --git a/test/unit_tests/braket/jobs/test_environment_variables.py b/test/unit_tests/braket/jobs/test_environment_variables.py index 2c8a2f546..dd1f12407 100644 --- a/test/unit_tests/braket/jobs/test_environment_variables.py +++ b/test/unit_tests/braket/jobs/test_environment_variables.py @@ -58,8 +58,9 @@ def test_hyperparameters(): "a": "a_val", "b": 2, } - with tempfile.TemporaryDirectory() as temp_dir, patch.dict( - os.environ, {"AMZN_BRAKET_HP_FILE": str(Path(temp_dir) / hp_file)} + with ( + tempfile.TemporaryDirectory() as temp_dir, + patch.dict(os.environ, {"AMZN_BRAKET_HP_FILE": str(Path(temp_dir) / hp_file)}), ): with open(str(Path(temp_dir) / hp_file), "w") as f: json.dump(hyperparameters, f) diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index c4d4a7a57..fa133ce52 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -311,7 +311,9 @@ def my_entry(c=0, d: float = 1.0, **extras): aws_session=aws_session, ) assert mock_tempdir.return_value.__exit__.called - _mock_open.assert_called_with(Path(mock_tempdir_name) / "requirements.txt", "w") + _mock_open.assert_called_with( + Path(mock_tempdir_name) / "requirements.txt", "w", encoding="utf-8" + ) _mock_open.return_value.__enter__.return_value.write.assert_called_with( "\n".join(dependency_list) ) diff --git a/test/unit_tests/braket/pulse/test_pulse_sequence.py b/test/unit_tests/braket/pulse/test_pulse_sequence.py index fa6eca395..20b3bbef4 100644 --- a/test/unit_tests/braket/pulse/test_pulse_sequence.py +++ b/test/unit_tests/braket/pulse/test_pulse_sequence.py @@ -69,15 +69,13 @@ def conflicting_user_defined_frame(): def test_pulse_sequence_with_user_defined_frame(user_defined_frame): pulse_sequence = PulseSequence().set_frequency(user_defined_frame, 6e6) - expected_str = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", - " set_frequency(user_defined_frame_0, 6000000.0);", - "}", - ] - ) + expected_str = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " frame user_defined_frame_0 = newframe(device_port_x0, 10000000.0, 3.14);", + " set_frequency(user_defined_frame_0, 6000000.0);", + "}", + ]) assert pulse_sequence.to_ir() == expected_str @@ -132,41 +130,38 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined ) .capture_v0(predefined_frame_2) ) - expected_str_unbound = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " bit[2] psb;", - *[ - f" input float {parameter};" - for parameter in reversed(list(pulse_sequence.parameters)) - ], - " waveform gauss_wf = gaussian(length_g * 1s, sigma_g * 1s, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(length_dg * 1s," - " sigma_dg * 1s, 0.2, 1, false);", - " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", - " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " waveform erf_square_wf = erf_square(length_es * 1s, width_es * 1s, 2.0ns," - " 8.0ns, 1, false);", - " set_frequency(predefined_frame_1, a + 2.0 * c);", - " shift_frequency(predefined_frame_1, a + 2.0 * c);", - " set_phase(predefined_frame_1, a + 2.0 * c);", - " shift_phase(predefined_frame_1, -1.0 * a + -2.0 * c);", - " set_scale(predefined_frame_1, a + 2.0 * c);", - " psb[0] = capture_v0(predefined_frame_1);", - " delay[(a + 2.0 * c) * 1s] predefined_frame_1, predefined_frame_2;", - " delay[(a + 2.0 * c) * 1s] predefined_frame_1;", - " delay[1.0ms] predefined_frame_1;", - " barrier predefined_frame_1, predefined_frame_2;", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_2, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_2, arb_wf);", - " play(predefined_frame_1, erf_square_wf);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) + expected_str_unbound = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " bit[2] psb;", + *[ + f" input float {parameter};" + for parameter in reversed(list(pulse_sequence.parameters)) + ], + " waveform gauss_wf = gaussian(length_g * 1s, sigma_g * 1s, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(length_dg * 1s, sigma_dg * 1s, 0.2, 1, false);", + " waveform constant_wf = constant(length_c * 1s, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(length_es * 1s, width_es * 1s, 2.0ns," + " 8.0ns, 1, false);", + " set_frequency(predefined_frame_1, a + 2.0 * c);", + " shift_frequency(predefined_frame_1, a + 2.0 * c);", + " set_phase(predefined_frame_1, a + 2.0 * c);", + " shift_phase(predefined_frame_1, -1.0 * a + -2.0 * c);", + " set_scale(predefined_frame_1, a + 2.0 * c);", + " psb[0] = capture_v0(predefined_frame_1);", + " delay[(a + 2.0 * c) * 1s] predefined_frame_1, predefined_frame_2;", + " delay[(a + 2.0 * c) * 1s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", + " barrier predefined_frame_1, predefined_frame_2;", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_2, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ]) assert pulse_sequence.to_ir() == expected_str_unbound assert pulse_sequence.parameters == { FreeParameter("a"), @@ -179,17 +174,15 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined FreeParameter("width_es"), FreeParameter("length_es"), } - b_bound = pulse_sequence.make_bound_pulse_sequence( - { - "c": 2, - "length_g": 1e-3, - "length_dg": 3e-3, - "sigma_dg": 0.4, - "length_c": 4e-3, - "length_es": 20e-9, - "width_es": 12e-9, - } - ) + b_bound = pulse_sequence.make_bound_pulse_sequence({ + "c": 2, + "length_g": 1e-3, + "length_dg": 3e-3, + "sigma_dg": 0.4, + "length_c": 4e-3, + "length_es": 20e-9, + "width_es": 12e-9, + }) b_bound_call = pulse_sequence( c=2, length_g=1e-3, @@ -199,70 +192,66 @@ def test_pulse_sequence_make_bound_pulse_sequence(predefined_frame_1, predefined length_es=20e-9, width_es=12e-9, ) - expected_str_b_bound = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " bit[2] psb;", - *[f" input float {parameter};" for parameter in reversed(list(b_bound.parameters))], - " waveform gauss_wf = gaussian(1.0ms, sigma_g * 1s, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", - " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", - " set_frequency(predefined_frame_1, a + 4.0);", - " shift_frequency(predefined_frame_1, a + 4.0);", - " set_phase(predefined_frame_1, a + 4.0);", - " shift_phase(predefined_frame_1, -1.0 * a + -4.0);", - " set_scale(predefined_frame_1, a + 4.0);", - " psb[0] = capture_v0(predefined_frame_1);", - " delay[(a + 4.0) * 1s] predefined_frame_1, predefined_frame_2;", - " delay[(a + 4.0) * 1s] predefined_frame_1;", - " delay[1.0ms] predefined_frame_1;", - " barrier predefined_frame_1, predefined_frame_2;", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_2, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_2, arb_wf);", - " play(predefined_frame_1, erf_square_wf);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) + expected_str_b_bound = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " bit[2] psb;", + *[f" input float {parameter};" for parameter in reversed(list(b_bound.parameters))], + " waveform gauss_wf = gaussian(1.0ms, sigma_g * 1s, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", + " set_frequency(predefined_frame_1, a + 4.0);", + " shift_frequency(predefined_frame_1, a + 4.0);", + " set_phase(predefined_frame_1, a + 4.0);", + " shift_phase(predefined_frame_1, -1.0 * a + -4.0);", + " set_scale(predefined_frame_1, a + 4.0);", + " psb[0] = capture_v0(predefined_frame_1);", + " delay[(a + 4.0) * 1s] predefined_frame_1, predefined_frame_2;", + " delay[(a + 4.0) * 1s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", + " barrier predefined_frame_1, predefined_frame_2;", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_2, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ]) assert b_bound.to_ir() == b_bound_call.to_ir() == expected_str_b_bound assert pulse_sequence.to_ir() == expected_str_unbound assert b_bound.parameters == {FreeParameter("sigma_g"), FreeParameter("a")} both_bound = b_bound.make_bound_pulse_sequence({"a": 1, "sigma_g": 0.7}) both_bound_call = b_bound_call(1, sigma_g=0.7) # use arg 1 for a - expected_str_both_bound = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " bit[2] psb;", - " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", - " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", - " set_frequency(predefined_frame_1, 5.0);", - " shift_frequency(predefined_frame_1, 5.0);", - " set_phase(predefined_frame_1, 5.0);", - " shift_phase(predefined_frame_1, -5.0);", - " set_scale(predefined_frame_1, 5.0);", - " psb[0] = capture_v0(predefined_frame_1);", - " delay[5.0s] predefined_frame_1, predefined_frame_2;", - " delay[5.0s] predefined_frame_1;", - " delay[1.0ms] predefined_frame_1;", - " barrier predefined_frame_1, predefined_frame_2;", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_2, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_2, arb_wf);", - " play(predefined_frame_1, erf_square_wf);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) + expected_str_both_bound = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " bit[2] psb;", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(20.0ns, 12.0ns, 2.0ns, 8.0ns, 1, false);", + " set_frequency(predefined_frame_1, 5.0);", + " shift_frequency(predefined_frame_1, 5.0);", + " set_phase(predefined_frame_1, 5.0);", + " shift_phase(predefined_frame_1, -5.0);", + " set_scale(predefined_frame_1, 5.0);", + " psb[0] = capture_v0(predefined_frame_1);", + " delay[5.0s] predefined_frame_1, predefined_frame_2;", + " delay[5.0s] predefined_frame_1;", + " delay[1.0ms] predefined_frame_1;", + " barrier predefined_frame_1, predefined_frame_2;", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_2, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ]) assert both_bound.to_ir() == both_bound_call.to_ir() == expected_str_both_bound assert b_bound.to_ir() == b_bound_call.to_ir() == expected_str_b_bound assert pulse_sequence.to_ir() == expected_str_unbound @@ -350,36 +339,34 @@ def test_pulse_sequence_to_ir(predefined_frame_1, predefined_frame_2): ) .capture_v0(predefined_frame_2) ) - expected_str = "\n".join( - [ - "OPENQASM 3.0;", - "cal {", - " bit[2] psb;", - " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", - " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", - " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", - " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", - " waveform erf_square_wf = erf_square(32.0ns, 20.0ns, 2.0ns, 8.0ns, 1, false);", - " set_frequency(predefined_frame_1, 3000000000.0);", - " shift_frequency(predefined_frame_1, 1000000000.0);", - " set_phase(predefined_frame_1, -0.5);", - " shift_phase(predefined_frame_1, 0.1);", - " set_scale(predefined_frame_1, 0.25);", - " psb[0] = capture_v0(predefined_frame_1);", - " delay[2.0ns] predefined_frame_1, predefined_frame_2;", - " delay[1.0us] predefined_frame_1;", - " delay[1.0ms] $0;", - " barrier $0, $1;", - " barrier predefined_frame_1, predefined_frame_2;", - " play(predefined_frame_1, gauss_wf);", - " play(predefined_frame_2, drag_gauss_wf);", - " play(predefined_frame_1, constant_wf);", - " play(predefined_frame_2, arb_wf);", - " play(predefined_frame_1, erf_square_wf);", - " psb[1] = capture_v0(predefined_frame_2);", - "}", - ] - ) + expected_str = "\n".join([ + "OPENQASM 3.0;", + "cal {", + " bit[2] psb;", + " waveform gauss_wf = gaussian(1.0ms, 700.0ms, 1, false);", + " waveform drag_gauss_wf = drag_gaussian(3.0ms, 400.0ms, 0.2, 1, false);", + " waveform constant_wf = constant(4.0ms, 2.0 + 0.3im);", + " waveform arb_wf = {1.0 + 0.4im, 0, 0.3, 0.1 + 0.2im};", + " waveform erf_square_wf = erf_square(32.0ns, 20.0ns, 2.0ns, 8.0ns, 1, false);", + " set_frequency(predefined_frame_1, 3000000000.0);", + " shift_frequency(predefined_frame_1, 1000000000.0);", + " set_phase(predefined_frame_1, -0.5);", + " shift_phase(predefined_frame_1, 0.1);", + " set_scale(predefined_frame_1, 0.25);", + " psb[0] = capture_v0(predefined_frame_1);", + " delay[2.0ns] predefined_frame_1, predefined_frame_2;", + " delay[1.0us] predefined_frame_1;", + " delay[1.0ms] $0;", + " barrier $0, $1;", + " barrier predefined_frame_1, predefined_frame_2;", + " play(predefined_frame_1, gauss_wf);", + " play(predefined_frame_2, drag_gauss_wf);", + " play(predefined_frame_1, constant_wf);", + " play(predefined_frame_2, arb_wf);", + " play(predefined_frame_1, erf_square_wf);", + " psb[1] = capture_v0(predefined_frame_2);", + "}", + ]) assert pulse_sequence.to_ir() == expected_str diff --git a/test/unit_tests/braket/quantum_information/test_pauli_string.py b/test/unit_tests/braket/quantum_information/test_pauli_string.py index c3959d305..9d5f7e5bb 100644 --- a/test/unit_tests/braket/quantum_information/test_pauli_string.py +++ b/test/unit_tests/braket/quantum_information/test_pauli_string.py @@ -225,7 +225,7 @@ def test_power(circ, n, circ_res): assert circ1 == PauliString(circ_res) -@pytest.mark.xfail(raises=ValueError) +@pytest.mark.xfail(raises=TypeError) @pytest.mark.parametrize( "circ, n, operation", [ diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 99ab7e345..1cac97e26 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -366,9 +366,14 @@ def test_get_compiled_circuit_no_metadata(result_obj_1): def test_measurement_counts_from_measurements(): - measurements: np.ndarray = np.array( - [[1, 0, 1, 0], [0, 0, 0, 0], [1, 0, 1, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 1, 0]] - ) + measurements: np.ndarray = np.array([ + [1, 0, 1, 0], + [0, 0, 0, 0], + [1, 0, 1, 0], + [1, 0, 0, 0], + [1, 0, 0, 0], + [1, 0, 1, 0], + ]) measurement_counts = GateModelQuantumTaskResult.measurement_counts_from_measurements( measurements ) @@ -509,20 +514,18 @@ def test_calculate_ir_results(ir_result, expected_result): instructions=[jaqcd.H(target=i) for i in range(4)], results=[ir_result] ).json() measured_qubits = [0, 1, 2, 3] - measurements = np.array( - [ - [0, 0, 1, 0], - [1, 1, 1, 1], - [1, 0, 0, 1], - [0, 0, 1, 0], - [1, 1, 1, 1], - [0, 1, 1, 1], - [0, 0, 0, 1], - [0, 1, 1, 1], - [0, 0, 0, 0], - [0, 0, 0, 1], - ] - ) + measurements = np.array([ + [0, 0, 1, 0], + [1, 1, 1, 1], + [1, 0, 0, 1], + [0, 0, 1, 0], + [1, 1, 1, 1], + [0, 1, 1, 1], + [0, 0, 0, 1], + [0, 1, 1, 1], + [0, 0, 0, 0], + [0, 0, 0, 1], + ]) result_types = GateModelQuantumTaskResult._calculate_result_types( ir_string, measurements, measured_qubits ) @@ -588,56 +591,54 @@ def test_hash_result_types(observable_1, observable_2): "braket.tasks.gate_model_quantum_task_result.GateModelQuantumTaskResult._calculate_result_types" ) def test_result_type_skips_computation_already_populated(calculate_result_types_mocked): - result_str = json.dumps( - { - "braketSchemaHeader": { - "name": "braket.task_result.gate_model_task_result", - "version": "1", - }, - "measurements": [[0]], - "resultTypes": [ - {"type": {"observable": ["z"], "targets": [0], "type": "variance"}, "value": 12.0} - ], - "measuredQubits": [0], - "taskMetadata": { - "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, - "id": "arn:aws:braket:us-east-1:1234567890:quantum-task/22a238b2-ae96", - "shots": 1, - "deviceId": "arn:aws:braket:::device/quantum-simulator/amazon/dm1", - "deviceParameters": { + result_str = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.gate_model_task_result", + "version": "1", + }, + "measurements": [[0]], + "resultTypes": [ + {"type": {"observable": ["z"], "targets": [0], "type": "variance"}, "value": 12.0} + ], + "measuredQubits": [0], + "taskMetadata": { + "braketSchemaHeader": {"name": "braket.task_result.task_metadata", "version": "1"}, + "id": "arn:aws:braket:us-east-1:1234567890:quantum-task/22a238b2-ae96", + "shots": 1, + "deviceId": "arn:aws:braket:::device/quantum-simulator/amazon/dm1", + "deviceParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.simulators." + "gate_model_simulator_device_parameters", + "version": "1", + }, + "paradigmParameters": { "braketSchemaHeader": { - "name": "braket.device_schema.simulators." - "gate_model_simulator_device_parameters", + "name": "braket.device_schema.gate_model_parameters", "version": "1", }, - "paradigmParameters": { - "braketSchemaHeader": { - "name": "braket.device_schema.gate_model_parameters", - "version": "1", - }, - "qubitCount": 1, - "disableQubitRewiring": False, - }, + "qubitCount": 1, + "disableQubitRewiring": False, }, - "createdAt": "2022-01-12T06:05:22.633Z", - "endedAt": "2022-01-12T06:05:24.136Z", - "status": "COMPLETED", }, - "additionalMetadata": { - "action": { - "braketSchemaHeader": {"name": "braket.ir.openqasm.program", "version": "1"}, - "source": "\nqubit[1] q;\nh q[0];\n#pragma braket result variance z(q[0])\n", - }, - "simulatorMetadata": { - "braketSchemaHeader": { - "name": "braket.task_result.simulator_metadata", - "version": "1", - }, - "executionDuration": 16, + "createdAt": "2022-01-12T06:05:22.633Z", + "endedAt": "2022-01-12T06:05:24.136Z", + "status": "COMPLETED", + }, + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.openqasm.program", "version": "1"}, + "source": "\nqubit[1] q;\nh q[0];\n#pragma braket result variance z(q[0])\n", + }, + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", }, + "executionDuration": 16, }, - } - ) + }, + }) res = GateModelQuantumTaskResult.from_string(result_str) assert ( res.get_value_by_result_type(ResultType.Variance(observable=Observable.Z(), target=[0])) diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py b/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py index 3c7c523bb..345561bfc 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task_batch.py @@ -21,9 +21,11 @@ RESULTS = [ GateModelQuantumTaskResult( - task_metadata=TaskMetadata( - **{"id": str(uuid.uuid4()), "deviceId": "default", "shots": 100} - ), + task_metadata=TaskMetadata(**{ + "id": str(uuid.uuid4()), + "deviceId": "default", + "shots": 100, + }), additional_metadata=None, measurements=np.array([[0, 1], [1, 0]]), measured_qubits=[0, 1], diff --git a/tox.ini b/tox.ini index f8da75234..d11118271 100644 --- a/tox.ini +++ b/tox.ini @@ -38,28 +38,23 @@ extras = test [testenv:linters] basepython = python3 skip_install = true +# Remove this to check what versions are installed for the env. This stops running pip freeze. +list_dependencies_command = echo deps = - {[testenv:isort]deps} - {[testenv:black]deps} - {[testenv:flake8]deps} + {[testenv:ruff-format]deps} + {[testenv:ruff-check]deps} commands = - # isort MUST come before black as it will revert any changes made by black - {[testenv:isort]commands} - {[testenv:black]commands} - {[testenv:flake8]commands} + {[testenv:ruff-format]commands} + {[testenv:ruff-check]commands} # Read only linter env [testenv:linters_check] basepython = python3 skip_install = true deps = - {[testenv:isort_check]deps} - {[testenv:black_check]deps} - {[testenv:flake8]deps} + {[testenv:ruff-check]deps} commands = - {[testenv:isort_check]commands} - {[testenv:black_check]commands} - {[testenv:flake8]commands} + {[testenv:ruff-check]commands} [testenv:flake8] basepython = python3 @@ -103,6 +98,23 @@ deps = commands = black --check ./ {posargs} + +[testenv:ruff-check] +basepython = python3 +skip_install = true +deps = + ruff +commands = + ruff check src {posargs} + +[testenv:ruff-format] +basepython = python3 +skip_install = true +deps = + ruff +commands = + ruff format . {posargs} + [testenv:docs] basepython = python3 deps = From f8aa8fbc94fce687696686211ad4e0ce360f2123 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Mar 2025 16:15:48 +0000 Subject: [PATCH 269/347] prepare release v1.90.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6fbe2f39..480ab7a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.90.2 (2025-03-10) + +### Bug Fixes and Other Changes + + * onboard to use ruff + ## v1.90.1 (2025-03-06) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 18f47ccd8..f0a0bd1b4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.2.dev0" +__version__ = "1.90.2" From 58593bcb4338f51615d2410aa72244b59399fcb1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 10 Mar 2025 16:15:48 +0000 Subject: [PATCH 270/347] update development version to v1.90.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f0a0bd1b4..b01157cf1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.2" +__version__ = "1.90.3.dev0" From 7962d97f306a5748ccec5e4095564ffed611c105 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 17 Mar 2025 16:52:35 -0700 Subject: [PATCH 271/347] feat: Add Forte Enterprise 1 (#1071) --- src/braket/devices/devices.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index d66ca6654..2cad66674 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -35,6 +35,7 @@ class _IonQ(str, Enum): Aria1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-1" Aria2 = "arn:aws:braket:us-east-1::device/qpu/ionq/Aria-2" Forte1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Forte-1" + ForteEnterprise1 = "arn:aws:braket:us-east-1::device/qpu/ionq/Forte-Enterprise-1" class _OQC(str, Enum): _Lucy = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" From 24e9f780568551b223b0043a7183f26f52f8ea94 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 18 Mar 2025 00:09:53 +0000 Subject: [PATCH 272/347] prepare release v1.91.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 480ab7a3b..4c6b2129c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.91.0 (2025-03-18) + +### Features + + * Add Forte Enterprise 1 + ## v1.90.2 (2025-03-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b01157cf1..fb2275100 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.90.3.dev0" +__version__ = "1.91.0" From 0268e0bcc722fcc029e52edc8c7cf6d37ec60b15 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 18 Mar 2025 00:09:53 +0000 Subject: [PATCH 273/347] update development version to v1.91.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fb2275100..88796d7c9 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.0" +__version__ = "1.91.1.dev0" From ecdf4d985889244173856673c7a12ea73a9a7881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 10:42:05 -0400 Subject: [PATCH 274/347] infra: bump actions/setup-python from 5.4.0 to 5.5.0 (#1072) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index a65d87658..e4ed2c598 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 69181b1fe..8937c697e 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 47cf1d1b1..d9699438e 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 63e63a488..152914efd 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 9a3e3431f..7f85da0a5 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0 + uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 with: python-version: '3.x' - name: Install wheel From a64be7e9ba1dfd2102fa6dbbabb5eafbad6c72e6 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:49:10 -0400 Subject: [PATCH 275/347] infra: refactor pytest configuration into the pyproject file (#1040) --- pyproject.toml | 22 +++ setup.cfg | 12 -- .../gate_model_device_testing_utils.py | 169 +++++++++--------- 3 files changed, 107 insertions(+), 96 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7d37cb26b..5b84f2ee2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,25 @@ + +[tool.pytest.ini_options] +xfail_strict = true +# https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html +addopts = "--verbose -n logical --durations=0 --durations-min=1 --dist worksteal" +testpaths = ["test/unit_tests",] +# Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, +# but once a resolution has been adopted we can drop this "ignore". +# Ref: https://github.com/pytest-dev/pytest-cov/issues/557 +filterwarnings = [ + "ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning", +] +norecursedirs = [ + ".tox", + ".git", + "*/migrations/*", + "*/static/*", + "docs", + "venv", + "*/{{cookiecutter.project_slug}}/*", +] + [tool.ruff] target-version = "py39" line-length = 100 diff --git a/setup.cfg b/setup.cfg index 0abcc1275..72e69bcd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,16 +1,4 @@ [aliases] test=pytest -[tool:pytest] -xfail_strict = true -# https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html -addopts = - --verbose -n logical --durations=0 --durations-min=1 --dist worksteal -testpaths = test/unit_tests -filterwarnings= - # Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, - # but once a resolution has been adopted we can drop this "ignore". - # Ref: https://github.com/pytest-dev/pytest-cov/issues/557 - ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning - diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 2227f31a3..b3e31e656 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -287,22 +287,21 @@ def result_types_tensor_x_y_testing(device: Device, run_kwargs: dict[str, Any]): varphi = -0.543 obs = Observable.X() @ Observable.Y() obs_targets = [0, 2] + expected_mean = np.sin(theta) * np.sin(phi) * np.sin(varphi) + expected_var = ( + 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 + - np.cos(2 * (theta - phi)) + - np.cos(2 * (theta + phi)) + + 2 * np.cos(2 * theta) + + 2 * np.cos(2 * phi) + + 14 + ) / 16 + expected_eigs = get_pauli_eigenvalues(1) circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = np.sin(theta) * np.sin(phi) * np.sin(varphi) - expected_var = ( - 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 - - np.cos(2 * (theta - phi)) - - np.cos(2 * (theta + phi)) - + 2 * np.cos(2 * theta) - + 2 * np.cos(2 * phi) - + 14 - ) / 16 - expected_eigs = get_pauli_eigenvalues(1) - assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -315,15 +314,15 @@ def result_types_tensor_z_z_testing(device: Device, run_kwargs: dict[str, Any]): varphi = -0.543 obs = Observable.Z() @ Observable.Z() obs_targets = [0, 2] + expected_mean = 0.849694136476246 + expected_var = 0.27801987443788634 + expected_eigs = get_pauli_eigenvalues(1) + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = 0.849694136476246 - expected_var = 0.27801987443788634 - expected_eigs = get_pauli_eigenvalues(1) - assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -343,15 +342,15 @@ def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: ]) obs = Observable.Hermitian(matrix1) @ Observable.Hermitian(matrix2) obs_targets = [0, 1, 2] + expected_mean = -4.30215023196904 + expected_var = 370.71292282796804 + expected_eigs = np.array([-70.90875406, -31.04969387, 0, 3.26468993, 38.693758]) + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = -4.30215023196904 - expected_var = 370.71292282796804 - expected_eigs = np.array([-70.90875406, -31.04969387, 0, 3.26468993, 38.693758]) - assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -364,21 +363,20 @@ def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: dict[str, Any] varphi = -0.543 obs = Observable.Z() @ Observable.H() @ Observable.Y() obs_targets = [0, 1, 2] + expected_mean = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2) + expected_var = ( + 3 + + np.cos(2 * phi) * np.cos(varphi) ** 2 + - np.cos(2 * theta) * np.sin(varphi) ** 2 + - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) + ) / 4 + expected_eigs = get_pauli_eigenvalues(1) + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt( - 2 - ) - expected_var = ( - 3 - + np.cos(2 * phi) * np.cos(varphi) ** 2 - - np.cos(2 * theta) * np.sin(varphi) ** 2 - - 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) - ) / 4 - expected_eigs = get_pauli_eigenvalues(1) assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -397,50 +395,51 @@ def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: dict[str ]) obs = Observable.Z() @ Observable.Hermitian(array) obs_targets = [0, 1, 2] + expected_mean = 0.5 * ( + -6 * np.cos(theta) * (np.cos(varphi) + 1) + - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) + + 3 * np.cos(varphi) * np.sin(phi) + + np.sin(phi) + ) + expected_var = ( + 1057 + - np.cos(2 * phi) + + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) + - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) + + 16 * np.sin(2 * phi) + - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) + - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2 + - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) + - 8 + * np.cos(theta) + * ( + 4 + * np.cos(phi) + * ( + 4 + + 8 * np.cos(varphi) + + np.cos(2 * varphi) + - (1 + 6 * np.cos(varphi)) * np.sin(varphi) + ) + + np.sin(phi) + * ( + 15 + + 8 * np.cos(varphi) + - 11 * np.cos(2 * varphi) + + 42 * np.sin(varphi) + + 3 * np.sin(2 * varphi) + ) + ) + ) / 16 + + z_array = np.diag([1, -1]) + expected_eigs = np.linalg.eigvalsh(np.kron(z_array, array)) + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = 0.5 * ( - -6 * np.cos(theta) * (np.cos(varphi) + 1) - - 2 * np.sin(varphi) * (np.cos(theta) + np.sin(phi) - 2 * np.cos(phi)) - + 3 * np.cos(varphi) * np.sin(phi) - + np.sin(phi) - ) - expected_var = ( - 1057 - - np.cos(2 * phi) - + 12 * (27 + np.cos(2 * phi)) * np.cos(varphi) - - 2 * np.cos(2 * varphi) * np.sin(phi) * (16 * np.cos(phi) + 21 * np.sin(phi)) - + 16 * np.sin(2 * phi) - - 8 * (-17 + np.cos(2 * phi) + 2 * np.sin(2 * phi)) * np.sin(varphi) - - 8 * np.cos(2 * theta) * (3 + 3 * np.cos(varphi) + np.sin(varphi)) ** 2 - - 24 * np.cos(phi) * (np.cos(phi) + 2 * np.sin(phi)) * np.sin(2 * varphi) - - 8 - * np.cos(theta) - * ( - 4 - * np.cos(phi) - * ( - 4 - + 8 * np.cos(varphi) - + np.cos(2 * varphi) - - (1 + 6 * np.cos(varphi)) * np.sin(varphi) - ) - + np.sin(phi) - * ( - 15 - + 8 * np.cos(varphi) - - 11 * np.cos(2 * varphi) - + 42 * np.sin(varphi) - + 3 * np.sin(2 * varphi) - ) - ) - ) / 16 - - z_array = np.diag([1, -1]) - expected_eigs = np.linalg.eigvalsh(np.kron(z_array, array)) assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -459,15 +458,16 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: dict[str ]) obs = Observable.Y() @ Observable.Hermitian(array) obs_targets = [0, 1, 2] + expected_mean = 1.4499810303182408 + expected_var = 74.03174647518193 + y_array = np.array([[0, -1j], [1j, 0]]) + expected_eigs = np.linalg.eigvalsh(np.kron(y_array, array)) + circuit = get_result_types_three_qubit_circuit(theta, phi, varphi, obs, obs_targets, shots) tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean = 1.4499810303182408 - expected_var = 74.03174647518193 - y_array = np.array([[0, -1j], [1j, 0]]) - expected_eigs = np.linalg.eigvalsh(np.kron(y_array, array)) assert_variance_expectation_sample_result( result, shots, expected_var, expected_mean, expected_eigs ) @@ -490,6 +490,19 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any] obs2_targets = [0, 2] obs3 = Observable.Y() @ Observable.Hermitian(array) obs3_targets = [0, 1, 2] + expected_mean1 = np.sin(theta) * np.sin(phi) * np.sin(varphi) + expected_var1 = ( + 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 + - np.cos(2 * (theta - phi)) + - np.cos(2 * (theta + phi)) + + 2 * np.cos(2 * theta) + + 2 * np.cos(2 * phi) + + 14 + ) / 16 + + expected_mean2 = 0.849694136476246 + expected_mean3 = 1.4499810303182408 + circuit = ( get_result_types_three_qubit_circuit(theta, phi, varphi, obs1, obs1_targets, shots) .expectation(obs2, obs2_targets) @@ -499,18 +512,6 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any] for task in tasks: result = device.run(task, **run_kwargs).result() - expected_mean1 = np.sin(theta) * np.sin(phi) * np.sin(varphi) - expected_var1 = ( - 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 - - np.cos(2 * (theta - phi)) - - np.cos(2 * (theta + phi)) - + 2 * np.cos(2 * theta) - + 2 * np.cos(2 * phi) - + 14 - ) / 16 - - expected_mean2 = 0.849694136476246 - expected_mean3 = 1.4499810303182408 assert np.allclose(result.values[0], expected_var1) assert np.allclose(result.values[1], expected_mean1) assert np.allclose(result.values[2], expected_mean2) From eb9fa41bb165b436aa9a193d347172b02f72ec5e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:30:52 -0400 Subject: [PATCH 276/347] fix: concatenate time series value with unpacking instead of + (#1076) --- .../circuits/text_diagram_builders/ascii_circuit_diagram.py | 1 - .../circuits/text_diagram_builders/text_circuit_diagram.py | 1 - .../circuits/text_diagram_builders/unicode_circuit_diagram.py | 1 - src/braket/timings/time_series.py | 3 +-- 4 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index 57b591173..c7e3dc8b3 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -167,7 +167,6 @@ def _create_diagram_column( return cls._create_output(symbols, connections, circuit_qubits, global_phase) # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] - # flake8: noqa: BCS005 @classmethod def _draw_symbol( cls, symbol: str, symbols_width: int, connection: Literal["above", "below", "both", "none"] diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index 08bde78c5..db8d5db21 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -68,7 +68,6 @@ def _create_diagram_column( """ # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] - # flake8: noqa: BCS005 @classmethod @abstractmethod def _draw_symbol( diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index df8ffc9ed..6285fd0c1 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -188,7 +188,6 @@ def _update_connections(qubits: QubitSet, connections: dict[Qubit, str]) -> None connections[qubits[0]] = "below" # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] - # flake8: noqa: BCS005 @classmethod def _draw_symbol( cls, diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index f01299155..2cbf86df3 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -259,8 +259,7 @@ def stitch( f"Boundary handler value {boundary} is not allowed. \ Possible options are: 'mean', 'left', 'right'." ) - - new_values = self.values()[:-1] + [bndry_val] + other.values()[1:] + new_values = [*self.values()[:-1], bndry_val, *other.values()[1:]] for t, v in zip(new_times, new_values): new_time_series.put(t, v) From 5c0254f8d597ad08d38ef5bdac3205224f1f0368 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Apr 2025 16:14:05 +0000 Subject: [PATCH 277/347] prepare release v1.91.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c6b2129c..d106fcda5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.91.1 (2025-04-21) + +### Bug Fixes and Other Changes + + * concatenate time series value with unpacking instead of + + ## v1.91.0 (2025-03-18) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 88796d7c9..f9a42b420 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.1.dev0" +__version__ = "1.91.1" From 08fe69cab2a6f9b7da44f48195648e7a8af989fc Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 21 Apr 2025 16:14:05 +0000 Subject: [PATCH 278/347] update development version to v1.91.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f9a42b420..5086224d4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.1" +__version__ = "1.91.2.dev0" From 0de943cc228aeafa92433c4a2398af2198439222 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Mon, 21 Apr 2025 14:51:31 -0700 Subject: [PATCH 279/347] change: handling capability upgrade issues (#1075) --- src/braket/aws/aws_device.py | 23 +++++++++++++------ test/unit_tests/braket/aws/test_aws_device.py | 17 ++++++++++++++ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index e2cec5106..f0e7ff0b3 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -22,6 +22,7 @@ from enum import Enum from typing import Any, ClassVar, Optional +import pydantic from botocore.errorfactory import ClientError from networkx import DiGraph, complete_graph, from_edgelist @@ -379,13 +380,21 @@ def _populate_properties(self, session: AwsSession) -> None: self._status = metadata.get("deviceStatus") self._type = AwsDeviceType(metadata.get("deviceType")) self._provider_name = metadata.get("providerName") - self._properties = BraketSchemaBase.parse_raw_schema(metadata.get("deviceCapabilities")) - device_poll_interval = self._properties.service.getTaskPollIntervalMillis - self._poll_interval_seconds = ( - device_poll_interval / 1000.0 - if device_poll_interval - else AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL - ) + try: + self._properties = BraketSchemaBase.parse_raw_schema(metadata.get("deviceCapabilities")) + device_poll_interval = self._properties.service.getTaskPollIntervalMillis + self._poll_interval_seconds = ( + device_poll_interval / 1000.0 + if device_poll_interval + else AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL + ) + except (pydantic.v1.ValidationError, pydantic.ValidationError): + warnings.warn( + f"Unable to determine device capabilities for '{self._arn}'." + " Please make sure you are using the latest version of amazon-braket-schemas.", + stacklevel=1, + ) + self._poll_interval_seconds = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL self._topology_graph = None self._frames = None self._ports = None diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index fd1bcacde..274357f29 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -466,6 +466,14 @@ def test_gate_model_sim_schema(): "tasks", ) +MOCK_GATE_MODEL_INVALID_CAPABILITIES_QPU = { + "deviceName": "Aspen-10", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": {}, +} + @pytest.fixture( params=[ @@ -677,6 +685,15 @@ def test_device_refresh_metadata(arn): _assert_device_fields(device, MOCK_GATE_MODEL_QPU_CAPABILITIES_2, MOCK_GATE_MODEL_QPU_2) +def test_get_device_invalid_capabilities(arn): + mock_session = Mock() + mock_session.get_device.return_value = MOCK_GATE_MODEL_INVALID_CAPABILITIES_QPU + mock_session.region = RIGETTI_REGION + device = AwsDevice(arn, mock_session) + assert device is not None + assert device.properties is None + + MOCK_PULSE_MODEL_QPU_PULSE_CAPABILITIES_JSON_1 = { "braketSchemaHeader": { "name": "braket.device_schema.pulse.pulse_device_action_properties", From 898401198bd2916b1ffd6049fc3a001d95e329d7 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Apr 2025 16:14:46 +0000 Subject: [PATCH 280/347] prepare release v1.91.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d106fcda5..8898ba1b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.91.2 (2025-04-22) + +### Bug Fixes and Other Changes + + * handling capability upgrade issues + ## v1.91.1 (2025-04-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5086224d4..a5ff13fa0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.2.dev0" +__version__ = "1.91.2" From d0958564a4456220e8330ecdde704db786f8ce99 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 22 Apr 2025 16:14:46 +0000 Subject: [PATCH 281/347] update development version to v1.91.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a5ff13fa0..584ea574f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.2" +__version__ = "1.91.3.dev0" From 0c844aa24e76f45ffd15b342922b990cfd8d6f6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 11:24:23 -0400 Subject: [PATCH 282/347] infra: bump actions/setup-python from 5.5.0 to 5.6.0 (#1078) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index e4ed2c598..db2fa102a 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.9' - name: Install dependencies diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 8937c697e..2167cd9bf 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -23,7 +23,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index d9699438e..e2b542389 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.x' - name: Install wheel diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 152914efd..e8cc296ff 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -26,7 +26,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: ${{ matrix.python-version }} - name: Install dependencies diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 7f85da0a5..8507c9ddf 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python - uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: '3.x' - name: Install wheel From 65b7449402da56c08cf4013a95ca41a4185b79f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 09:24:01 -0400 Subject: [PATCH 283/347] infra: bump codecov/codecov-action from 5.4.0 to 5.4.2 (#1079) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e8cc296ff..b32441b0b 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 + uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 93beef1d52b8b47ad48212753a1d96a0e36c70b6 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 15 May 2025 14:52:46 -0400 Subject: [PATCH 284/347] infra: linter updates (#1080) --- .../ahs/analog_hamiltonian_simulation.py | 3 +- src/braket/aws/aws_device.py | 16 ++++----- src/braket/aws/aws_quantum_task.py | 36 +++++++++---------- src/braket/aws/aws_quantum_task_batch.py | 5 +-- src/braket/aws/aws_session.py | 2 +- src/braket/circuits/braket_program_context.py | 4 +-- src/braket/circuits/circuit.py | 8 ++--- src/braket/circuits/compiler_directives.py | 1 + src/braket/circuits/gates.py | 2 +- src/braket/circuits/noises.py | 2 +- src/braket/circuits/result_types.py | 1 + src/braket/circuits/translations.py | 7 ++-- src/braket/circuits/unitary_calculation.py | 2 +- src/braket/devices/device.py | 5 +-- src/braket/devices/local_simulator.py | 15 ++++---- src/braket/error_mitigation/debias.py | 1 + src/braket/jobs/data_persistence.py | 3 +- src/braket/jobs/local/local_job.py | 3 +- ...iltonian_simulation_quantum_task_result.py | 1 - .../tasks/annealing_quantum_task_result.py | 2 +- .../tasks/gate_model_quantum_task_result.py | 6 ++-- .../photonic_model_quantum_task_result.py | 1 - 22 files changed, 67 insertions(+), 59 deletions(-) diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index d22e4431b..bd6ee4863 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -18,12 +18,13 @@ from typing import TYPE_CHECKING import braket.ir.ahs as ir +from braket.device_schema import DeviceActionType + from braket.ahs.atom_arrangement import AtomArrangement, SiteType from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties from braket.ahs.driving_field import DrivingField from braket.ahs.hamiltonian import Hamiltonian from braket.ahs.local_detuning import LocalDetuning -from braket.device_schema import DeviceActionType from braket.timings.time_series import TimeSeries if TYPE_CHECKING: diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index f0e7ff0b3..7672f12e9 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -24,6 +24,14 @@ import pydantic from botocore.errorfactory import ClientError +from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties +from braket.device_schema.dwave import DwaveProviderProperties + +# TODO: Remove device_action module once this is added to init in the schemas repo +from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties +from braket.ir.blackbird import Program as BlackbirdProgram +from braket.ir.openqasm import Program as OpenQasmProgram +from braket.schema_common import BraketSchemaBase from networkx import DiGraph, complete_graph, from_edgelist from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation @@ -35,19 +43,11 @@ from braket.circuits import Circuit, Gate, QubitSet from braket.circuits.gate_calibrations import GateCalibrations from braket.circuits.noise_model import NoiseModel -from braket.device_schema import DeviceCapabilities, ExecutionDay, GateModelQpuParadigmProperties -from braket.device_schema.dwave import DwaveProviderProperties - -# TODO: Remove device_action module once this is added to init in the schemas repo -from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties from braket.devices.device import Device -from braket.ir.blackbird import Program as BlackbirdProgram -from braket.ir.openqasm import Program as OpenQasmProgram from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import _is_float from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence from braket.pulse.waveforms import _parse_waveform_from_calibration_schema -from braket.schema_common import BraketSchemaBase class AwsDeviceType(str, Enum): diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 0f47d18ec..66bbc17fa 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -22,22 +22,6 @@ from typing import Any, ClassVar, Optional, Union import boto3 - -from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation -from braket.annealing.problem import Problem -from braket.aws.aws_session import AwsSession -from braket.aws.queue_information import QuantumTaskQueueInfo, QueueType -from braket.circuits import Instruction -from braket.circuits.circuit import Circuit, Gate, QubitSet -from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.circuits.compiler_directives import StartVerbatimBox -from braket.circuits.gates import PulseGate -from braket.circuits.serialization import ( - IRType, - OpenQASMSerializationProperties, - QubitReferenceType, - SerializableProgram, -) from braket.device_schema import GateModelParameters from braket.device_schema.dwave import ( Dwave2000QDeviceParameters, @@ -54,10 +38,8 @@ from braket.device_schema.oqc import OqcDeviceParameters from braket.device_schema.rigetti import RigettiDeviceParameters from braket.device_schema.simulators import GateModelSimulatorDeviceParameters -from braket.error_mitigation import ErrorMitigation from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQASMProgram -from braket.pulse.pulse_sequence import PulseSequence from braket.schema_common import BraketSchemaBase from braket.task_result import ( AnalogHamiltonianSimulationTaskResult, @@ -65,6 +47,24 @@ GateModelTaskResult, PhotonicModelTaskResult, ) + +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation +from braket.annealing.problem import Problem +from braket.aws.aws_session import AwsSession +from braket.aws.queue_information import QuantumTaskQueueInfo, QueueType +from braket.circuits import Instruction +from braket.circuits.circuit import Circuit, Gate, QubitSet +from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.gates import PulseGate +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, + SerializableProgram, +) +from braket.error_mitigation import ErrorMitigation +from braket.pulse.pulse_sequence import PulseSequence from braket.tasks import ( AnalogHamiltonianSimulationQuantumTaskResult, AnnealingQuantumTaskResult, diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 106db698a..d82f1eb89 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -18,14 +18,15 @@ from itertools import repeat from typing import TYPE_CHECKING, Any, Optional +from braket.ir.blackbird import Program as BlackbirdProgram +from braket.ir.openqasm import Program as OpenQasmProgram + from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing import Problem from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit from braket.circuits.gate import Gate -from braket.ir.blackbird import Program as BlackbirdProgram -from braket.ir.openqasm import Program as OpenQasmProgram from braket.pulse.pulse_sequence import PulseSequence from braket.registers.qubit_set import QubitSet from braket.tasks.quantum_task_batch import QuantumTaskBatch diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index c8bad13e9..7f502c336 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -24,11 +24,11 @@ import backoff import boto3 +import braket._schemas as braket_schemas from botocore import awsrequest, client from botocore.config import Config from botocore.exceptions import ClientError -import braket._schemas as braket_schemas import braket._sdk as braket_sdk from braket.tracking.tracking_context import active_trackers, broadcast_event from braket.tracking.tracking_events import _TaskCreationEvent, _TaskStatusEvent diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 558692510..a93bf0994 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -15,6 +15,8 @@ from typing import Optional, Union import numpy as np +from braket.default_simulator.openqasm.program_context import AbstractProgramContext +from braket.ir.jaqcd.program_v1 import Results from sympy import Expr, Number from braket.circuits import Circuit, Instruction @@ -26,8 +28,6 @@ braket_result_to_result_type, one_prob_noise_map, ) -from braket.default_simulator.openqasm.program_context import AbstractProgramContext -from braket.ir.jaqcd.program_v1 import Results from braket.parametric import FreeParameterExpression diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index e9a80dbcd..5962cad55 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -20,6 +20,10 @@ import numpy as np import oqpy +from braket.default_simulator.openqasm.interpreter import Interpreter +from braket.ir.jaqcd import Program as JaqcdProgram +from braket.ir.openqasm import Program as OpenQasmProgram +from braket.ir.openqasm.program_v1 import io_type from sympy import Expr from braket.circuits import compiler_directives @@ -54,10 +58,6 @@ ) from braket.circuits.text_diagram_builders.unicode_circuit_diagram import UnicodeCircuitDiagram from braket.circuits.unitary_calculation import calculate_unitary_big_endian -from braket.default_simulator.openqasm.interpreter import Interpreter -from braket.ir.jaqcd import Program as JaqcdProgram -from braket.ir.openqasm import Program as OpenQasmProgram -from braket.ir.openqasm.program_v1 import io_type from braket.pulse.ast.qasm_parser import ast_to_qasm from braket.pulse.frame import Frame from braket.pulse.pulse_sequence import PulseSequence, _validate_uniqueness diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 9376d338d..c039b561f 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -14,6 +14,7 @@ from typing import Any import braket.ir.jaqcd as ir + from braket.circuits.compiler_directive import CompilerDirective diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 954f5c275..dafc40c73 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -17,10 +17,10 @@ from copy import deepcopy from typing import Any +import braket.ir.jaqcd as ir import numpy as np from oqpy import Program -import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.angled_gate import ( AngledGate, diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index a167b2a1c..777cfa81a 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -15,9 +15,9 @@ from collections.abc import Iterable from typing import Any, ClassVar, Union +import braket.ir.jaqcd as ir import numpy as np -import braket.ir.jaqcd as ir from braket.circuits import circuit from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 9cd42e5b9..df62f88f9 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -16,6 +16,7 @@ import re import braket.ir.jaqcd as ir + from braket.circuits import circuit from braket.circuits.free_parameter import FreeParameter from braket.circuits.observable import Observable diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index e67e3483f..3cefa4d89 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -17,10 +17,7 @@ from functools import reduce, singledispatch from typing import NoReturn -import braket.circuits.gates as braket_gates -import braket.circuits.result_types as ResultTypes # noqa: N812 import braket.ir.jaqcd.shared_models as models -from braket.circuits import Observable, noises, observables from braket.ir.jaqcd import ( Amplitude, DensityMatrix, @@ -32,6 +29,10 @@ ) from braket.ir.jaqcd.program_v1 import Results +import braket.circuits.gates as braket_gates +import braket.circuits.result_types as ResultTypes # noqa: N812 +from braket.circuits import Observable, noises, observables + BRAKET_GATES = { "gphase": braket_gates.GPhase, "i": braket_gates.I, diff --git a/src/braket/circuits/unitary_calculation.py b/src/braket/circuits/unitary_calculation.py index 9fa404284..4bd4fb366 100644 --- a/src/braket/circuits/unitary_calculation.py +++ b/src/braket/circuits/unitary_calculation.py @@ -14,12 +14,12 @@ from collections.abc import Iterable import numpy as np +from braket.default_simulator.linalg_utils import multiply_matrix from scipy.linalg import fractional_matrix_power from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction -from braket.default_simulator.linalg_utils import multiply_matrix from braket.registers.qubit_set import QubitSet diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index da3e37fab..57b7aa7f2 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -15,13 +15,14 @@ from abc import ABC, abstractmethod from typing import Any, Optional, Union +from braket.device_schema import DeviceActionType +from braket.ir.openqasm import Program + from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.circuits import Circuit, Noise from braket.circuits.noise_model import NoiseModel from braket.circuits.translations import SUPPORTED_NOISE_PRAGMA_TO_NOISE -from braket.device_schema import DeviceActionType -from braket.ir.openqasm import Program from braket.tasks.quantum_task import QuantumTask from braket.tasks.quantum_task_batch import QuantumTaskBatch diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 4371f8bc0..8dc2b737c 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -19,14 +19,7 @@ from os import cpu_count from typing import Any, Optional -from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation -from braket.annealing.problem import Problem -from braket.circuits import Circuit -from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.circuits.noise_model import NoiseModel -from braket.circuits.serialization import IRType, SerializableProgram from braket.device_schema import DeviceActionType, DeviceCapabilities -from braket.devices.device import Device from braket.ir.ahs import Program as AHSProgram from braket.ir.openqasm import Program as OpenQASMProgram from braket.simulator import BraketSimulator @@ -35,6 +28,14 @@ AnnealingTaskResult, GateModelTaskResult, ) + +from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation +from braket.annealing.problem import Problem +from braket.circuits import Circuit +from braket.circuits.circuit_helpers import validate_circuit_and_shots +from braket.circuits.noise_model import NoiseModel +from braket.circuits.serialization import IRType, SerializableProgram +from braket.devices.device import Device from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( AnalogHamiltonianSimulationQuantumTaskResult, diff --git a/src/braket/error_mitigation/debias.py b/src/braket/error_mitigation/debias.py index 8beddc7ef..2c6548663 100644 --- a/src/braket/error_mitigation/debias.py +++ b/src/braket/error_mitigation/debias.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. from braket.device_schema import error_mitigation + from braket.error_mitigation.error_mitigation import ErrorMitigation diff --git a/src/braket/jobs/data_persistence.py b/src/braket/jobs/data_persistence.py index 607901a3a..87c585b31 100644 --- a/src/braket/jobs/data_persistence.py +++ b/src/braket/jobs/data_persistence.py @@ -16,9 +16,10 @@ from pathlib import Path from typing import Any +from braket.jobs_data import PersistedJobData, PersistedJobDataFormat + from braket.jobs.environment_variables import get_checkpoint_dir, get_job_name, get_results_dir from braket.jobs.serialization import deserialize_values, serialize_values -from braket.jobs_data import PersistedJobData, PersistedJobDataFormat def save_job_checkpoint( diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index cac12a097..57c19c0d3 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -17,6 +17,8 @@ import time from typing import Any +from braket.jobs_data import PersistedJobData + from braket.aws.aws_session import AwsSession from braket.jobs.config import CheckpointConfig, OutputDataConfig, S3DataSourceConfig from braket.jobs.image_uris import Framework, retrieve_image @@ -27,7 +29,6 @@ from braket.jobs.quantum_job import QuantumJob from braket.jobs.quantum_job_creation import prepare_quantum_job from braket.jobs.serialization import deserialize_values -from braket.jobs_data import PersistedJobData class LocalQuantumJob(QuantumJob): diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index 8f72e7465..da8cdb583 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -18,7 +18,6 @@ from enum import Enum import numpy as np - from braket.task_result import ( AdditionalMetadata, AnalogHamiltonianSimulationTaskResult, diff --git a/src/braket/tasks/annealing_quantum_task_result.py b/src/braket/tasks/annealing_quantum_task_result.py index 31b055cbc..4c2d6a3a7 100644 --- a/src/braket/tasks/annealing_quantum_task_result.py +++ b/src/braket/tasks/annealing_quantum_task_result.py @@ -17,9 +17,9 @@ from dataclasses import dataclass import numpy as np +from braket.task_result import AdditionalMetadata, AnnealingTaskResult, TaskMetadata from braket.annealing import ProblemType -from braket.task_result import AdditionalMetadata, AnnealingTaskResult, TaskMetadata @dataclass diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 577a74d1b..f1effa71f 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -22,9 +22,6 @@ from typing import Any, TypeVar import numpy as np - -from braket.circuits import Observable, ResultType, StandardObservable -from braket.circuits.observables import TensorProduct, observable_from_ir from braket.ir.jaqcd import Expectation, Probability, Sample, Variance from braket.task_result import ( AdditionalMetadata, @@ -33,6 +30,9 @@ TaskMetadata, ) +from braket.circuits import Observable, ResultType, StandardObservable +from braket.circuits.observables import TensorProduct, observable_from_ir + T = TypeVar("T") diff --git a/src/braket/tasks/photonic_model_quantum_task_result.py b/src/braket/tasks/photonic_model_quantum_task_result.py index 59eba68ac..41f5974a2 100644 --- a/src/braket/tasks/photonic_model_quantum_task_result.py +++ b/src/braket/tasks/photonic_model_quantum_task_result.py @@ -16,7 +16,6 @@ from dataclasses import dataclass import numpy as np - from braket.task_result import AdditionalMetadata, PhotonicModelTaskResult, TaskMetadata From 7c2be69a98b29132faf0367897f3730d608e7f42 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 22 May 2025 17:59:17 -0400 Subject: [PATCH 285/347] infra: add coverage to public-main branch (#1084) --- .github/workflows/check-format.yml | 1 + .github/workflows/python-package.yml | 1 + .github/workflows/twine-check.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index db2fa102a..897d3622c 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + - public-main - feature/** permissions: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b32441b0b..817b78836 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,6 +10,7 @@ on: pull_request: branches: - main + - public-main - feature/** permissions: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 8507c9ddf..628155830 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - public-main - feature/** permissions: From 001cd1f8a19cfef4015b3e6881c2146ac0dd6956 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Wed, 4 Jun 2025 17:08:19 -0400 Subject: [PATCH 286/347] infra: ignore linter error PLC0207 to unblock CI (#1090) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 5b84f2ee2..fbd64cfe6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ lint.ignore = [ "N803", # Allow uppercase "N806", # Vars will break this rule based on technical names. "N815", # Allowing reviwers to do a per case basis + "PLC0207", # Accessing only the first or last element of `str.split()` without setting `maxsplit=1` "PLC2701", # Allow private function access in code "PLR0913", # Too many variables in function "PLR0914", # Too many local variables From 6a1682f45dfda3ddd98b4fa86c2f3ecef0831b47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 21:15:40 +0000 Subject: [PATCH 287/347] infra: bump codecov/codecov-action from 5.4.2 to 5.4.3 (#1088) --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 817b78836..2131630bf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,7 +37,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2 + uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From b1114c71a03ba6f11a67c6294327d2d3d935d2b8 Mon Sep 17 00:00:00 2001 From: Sai Nandan Morapakula Date: Tue, 10 Jun 2025 11:38:45 -0400 Subject: [PATCH 288/347] feat: Measure Criteria for readout error to target measurement operations (#1087) Co-authored-by: Tim (Yi-Ting) --- src/braket/circuits/noise_model/__init__.py | 1 + .../circuits/noise_model/measure_criteria.py | 110 ++++++++++++ .../circuits/noise_model/noise_model.py | 86 ++++++++-- .../circuits/noise/test_measure_criteria.py | 157 ++++++++++++++++++ .../braket/circuits/noise/test_noise_model.py | 86 ++++++++++ 5 files changed, 430 insertions(+), 10 deletions(-) create mode 100644 src/braket/circuits/noise_model/measure_criteria.py create mode 100644 test/unit_tests/braket/circuits/noise/test_measure_criteria.py diff --git a/src/braket/circuits/noise_model/__init__.py b/src/braket/circuits/noise_model/__init__.py index f1d25637b..f1a6ae637 100644 --- a/src/braket/circuits/noise_model/__init__.py +++ b/src/braket/circuits/noise_model/__init__.py @@ -21,6 +21,7 @@ ) from braket.circuits.noise_model.gate_criteria import GateCriteria # noqa: F401 from braket.circuits.noise_model.initialization_criteria import InitializationCriteria # noqa: F401 +from braket.circuits.noise_model.measure_criteria import MeasureCriteria # noqa: F401 from braket.circuits.noise_model.noise_model import ( NoiseModel, # noqa: F401 NoiseModelInstruction, # noqa: F401 diff --git a/src/braket/circuits/noise_model/measure_criteria.py b/src/braket/circuits/noise_model/measure_criteria.py new file mode 100644 index 000000000..d11b528bd --- /dev/null +++ b/src/braket/circuits/noise_model/measure_criteria.py @@ -0,0 +1,110 @@ +# 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. + +from collections.abc import Iterable +from typing import Any, Optional, Union + +from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure +from braket.circuits.noise_model.circuit_instruction_criteria import CircuitInstructionCriteria +from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult +from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input +from braket.registers.qubit_set import QubitSetInput + + +class MeasureCriteria(CircuitInstructionCriteria): + """This class models noise Criteria based on Measure instructions.""" + + def __init__(self, qubits: Optional[QubitSetInput] = None): + """Creates Measure-based Criteria. + + Args: + qubits (Optional[QubitSetInput]): A set of relevant qubits. If no qubits + are provided, all (possible) qubits are considered to be relevant. + """ + self._qubits = parse_qubit_input(qubits, 1) + + def __str__(self): + return f"{self.__class__.__name__}({self._qubits})" + + def __repr__(self): + return f"{self.__class__.__name__}(qubits={self._qubits})" + + def applicable_key_types(self) -> Iterable[CriteriaKey]: + """Returns an Iterable of criteria keys. + + Returns: + Iterable[CriteriaKey]: This Criteria operates on Qubits. + """ + return [CriteriaKey.QUBIT] + + def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + """Gets the keys for a given CriteriaKey. + + Args: + key_type (CriteriaKey): The relevant Criteria Key. + + Returns: + Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type: + QUBIT will return a set of qubit targets that are relevant to this Criteria, or + CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) qubits. + All other keys will return an empty set. + """ + if key_type == CriteriaKey.QUBIT: + return CriteriaKeyResult.ALL if self._qubits is None else set(self._qubits) + return set() + + def to_dict(self) -> dict: + """Converts a dictionary representing an object of this class into an instance of + this class. + + Returns: + dict: A dictionary representing the serialized version of this Criteria. + """ + qubits = list(self._qubits) if self._qubits is not None else None + return { + "__class__": self.__class__.__name__, + "qubits": qubits, + } + + def instruction_matches(self, instruction: Instruction) -> bool: + """Returns true if an Instruction matches the criteria. + + Args: + instruction (Instruction): An Instruction to match. + + Returns: + bool: Returns true if the instruction is a Measure instruction and the target + is a qubit (or set of qubits) provided in the constructor. + If qubits were not provided in the constructor, then this method will accept any + Measure instruction target. + """ + if not isinstance(instruction.operator, Measure): + return False + return CircuitInstructionCriteria._check_target_in_qubits(self._qubits, instruction.target) + + @classmethod + def from_dict(cls, criteria: dict) -> Criteria: + """Deserializes a dictionary into a Criteria object. + + Args: + criteria (dict): A dictionary representation of a MeasureCriteria. + + Returns: + Criteria: A deserialized MeasureCriteria represented by the passed in + serialized data. + """ + return MeasureCriteria(criteria["qubits"]) + + +Criteria.register_criteria(MeasureCriteria) diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index cca792e08..46c1b6215 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -19,10 +19,12 @@ from braket.circuits.circuit import Circuit from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.noise import Noise from braket.circuits.noise_model.circuit_instruction_criteria import CircuitInstructionCriteria from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult from braket.circuits.noise_model.initialization_criteria import InitializationCriteria +from braket.circuits.noise_model.measure_criteria import MeasureCriteria from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria from braket.circuits.result_types import ObservableResultType from braket.registers.qubit_set import QubitSetInput @@ -181,6 +183,8 @@ def get_instructions_by_type(self) -> NoiseModelInstructions: for item in self._instructions: if isinstance(item.criteria, InitializationCriteria): init_noise.append(item) + elif isinstance(item.criteria, MeasureCriteria): + readout_noise.append(item) elif isinstance(item.criteria, CircuitInstructionCriteria): gate_noise.append(item) elif isinstance(item.criteria, ResultTypeCriteria): @@ -271,14 +275,14 @@ def _apply_gate_noise( new_circuit = Circuit() for circuit_instruction in circuit.instructions: new_circuit.add_instruction(circuit_instruction) - target_qubits = list(circuit_instruction.target) - for item in gate_noise_instructions: - if item.criteria.instruction_matches(circuit_instruction): - if item.noise.fixed_qubit_count() == len(target_qubits): - new_circuit.add_instruction(Instruction(item.noise, target_qubits)) - else: - for qubit in target_qubits: - new_circuit.add_instruction(Instruction(item.noise, qubit)) + + if not isinstance(circuit_instruction.operator, Measure): + new_circuit = _apply_noise_on_instruction( + new_circuit, + circuit_instruction, + gate_noise_instructions, + ) + for result_type in circuit.result_types: new_circuit.add_result_type(result_type) return new_circuit @@ -315,6 +319,7 @@ def _apply_readout_noise( readout_noise_instructions: list[NoiseModelInstruction], ) -> Circuit: """Applies the readout noise of this noise model to a circuit and returns the circuit. + This includes both observable result types and measurement instructions. Args: circuit (Circuit): A circuit to apply `noise` to. @@ -324,9 +329,11 @@ def _apply_readout_noise( Returns: Circuit: The passed in circuit, with the readout noise applied. """ - if not readout_noise_instructions or not circuit.result_types: + if not readout_noise_instructions: return circuit - return _apply_noise_on_observable_result_types(circuit, readout_noise_instructions) + + new_circuit = _apply_noise_on_measurements(circuit, readout_noise_instructions) + return _apply_noise_on_observable_result_types(new_circuit, readout_noise_instructions) @classmethod def _items_to_string( @@ -366,6 +373,34 @@ def from_dict(cls, noise_dict: dict) -> NoiseModel: return model +def _apply_noise_on_instruction( + circuit: Circuit, + instruction: Instruction, + noise_instructions: list[NoiseModelInstruction], +) -> Circuit: + """Applies noise to a single instruction based on the noise instructions. + + Args: + circuit (Circuit): The circuit to add noise instructions to. + instruction (Instruction): The instruction to apply noise to. + noise_instructions (list[NoiseModelInstruction]): List of noise instructions to apply. + + Returns: + Circuit: The passed in circuit, with the instruction noise applied. + """ + target_qubits = list(instruction.target) + for item in noise_instructions: + if not item.criteria.instruction_matches(instruction): + continue + + if item.noise.fixed_qubit_count() == len(target_qubits): + circuit.add_instruction(Instruction(item.noise, target_qubits)) + else: + for qubit in target_qubits: + circuit.add_instruction(Instruction(item.noise, qubit)) + return circuit + + def _apply_noise_on_observable_result_types( circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction] ) -> Circuit: @@ -394,3 +429,34 @@ def _apply_noise_on_observable_result_types( item = readout_noise_instructions[noise_item_index] circuit.apply_readout_noise(item.noise, qubit) return circuit + + +def _apply_noise_on_measurements( + circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction] +) -> Circuit: + """Applies readout noise to measurement instructions. + + Args: + circuit (Circuit): The circuit to apply the readout noise to. + readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise + to apply. + + Returns: + Circuit: The passed in circuit, with the readout noise on measurements applied. + """ + new_circuit = Circuit() + for instruction in circuit.instructions: + if not isinstance(instruction.operator, Measure): + new_circuit.add_instruction(instruction) + continue + for noise_instruction in readout_noise_instructions: + if isinstance( + noise_instruction.criteria, MeasureCriteria + ) and noise_instruction.criteria.instruction_matches(instruction): + new_circuit.add_instruction( + Instruction(noise_instruction.noise, instruction.target) + ) + new_circuit.add_instruction(instruction) + for result_type in circuit.result_types: + new_circuit.add_result_type(result_type) + return new_circuit diff --git a/test/unit_tests/braket/circuits/noise/test_measure_criteria.py b/test/unit_tests/braket/circuits/noise/test_measure_criteria.py new file mode 100644 index 000000000..cb67ee0f9 --- /dev/null +++ b/test/unit_tests/braket/circuits/noise/test_measure_criteria.py @@ -0,0 +1,157 @@ +import pytest +from unittest.mock import Mock + +from braket.circuits import Circuit, Instruction +from braket.circuits.measure import Measure +from braket.circuits.noise_model.measure_criteria import MeasureCriteria +from braket.circuits.noise_model.criteria import CriteriaKey, CriteriaKeyResult +from braket.circuits.noise_model.noise_model import NoiseModel +from braket.circuits.noises import BitFlip, PhaseFlip + + +def test_measure_criteria_initialization(): + # Test initialization with no qubits + criteria = MeasureCriteria() + assert criteria._qubits is None + + # Test initialization with single qubit + criteria = MeasureCriteria(qubits=0) + assert criteria._qubits == {0} + + # Test initialization with multiple qubits + criteria = MeasureCriteria(qubits=[0, 1]) + assert criteria._qubits == {0, 1} + + +def test_measure_criteria_string_representation(): + # Test string representation with no qubits + criteria = MeasureCriteria() + assert str(criteria) == "MeasureCriteria(None)" + assert repr(criteria) == "MeasureCriteria(qubits=None)" + + # Test string representation with qubits + criteria = MeasureCriteria(qubits=[0, 1]) + assert str(criteria) == "MeasureCriteria({0, 1})" + assert repr(criteria) == "MeasureCriteria(qubits={0, 1})" + + +def test_measure_criteria_applicable_key_types(): + criteria = MeasureCriteria() + assert list(criteria.applicable_key_types()) == [CriteriaKey.QUBIT] + + +def test_measure_criteria_get_keys(): + # Test with no qubits specified + criteria = MeasureCriteria() + assert criteria.get_keys(CriteriaKey.QUBIT) == CriteriaKeyResult.ALL + assert criteria.get_keys(CriteriaKey.GATE) == set() + + # Test with specific qubits + criteria = MeasureCriteria(qubits=[0, 1]) + assert criteria.get_keys(CriteriaKey.QUBIT) == {0, 1} + assert criteria.get_keys(CriteriaKey.GATE) == set() + + +def test_measure_criteria_instruction_matches(): + criteria = MeasureCriteria(qubits=[0, 1]) + + # Test with matching measure instruction + measure_instruction = Instruction(Measure(), 0) + assert criteria.instruction_matches(measure_instruction) is True + + # Test with non-matching qubit + measure_instruction = Instruction(Measure(), 2) + assert criteria.instruction_matches(measure_instruction) is False + + # Test with non-measure instruction + non_measure_instruction = Mock(spec=Instruction) + non_measure_instruction.operator = Mock() + assert criteria.instruction_matches(non_measure_instruction) is False + + +def test_measure_criteria_serialization(): + # Test serialization with no qubits + criteria = MeasureCriteria() + serialized = criteria.to_dict() + assert serialized == {"__class__": "MeasureCriteria", "qubits": None} + deserialized = MeasureCriteria.from_dict(serialized) + assert deserialized._qubits is None + + # Test serialization with qubits + criteria = MeasureCriteria(qubits=[0, 1]) + serialized = criteria.to_dict() + assert serialized == {"__class__": "MeasureCriteria", "qubits": [0, 1]} + deserialized = MeasureCriteria.from_dict(serialized) + assert deserialized._qubits == {0, 1} + + +@pytest.mark.parametrize( + "noise_model, input_circuit, expected_circuit", + [ + ( + # Test single qubit measurement with noise + NoiseModel().add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0])), + Circuit().x(0).measure(0), + Circuit().x(0).bit_flip(0, 0.1).measure(0), + ), + ( + # Test multiple qubit measurements with noise + NoiseModel().add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0, 1])), + Circuit().x(0).x(1).measure([0, 1]), + Circuit().x(0).x(1).bit_flip(0, 0.1).measure(0).bit_flip(1, 0.1).measure(1), + ), + ( + # Test measurement on non-targeted qubit + NoiseModel().add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0])), + Circuit().x(1).measure(1), + Circuit().x(1).measure(1), + ), + ( + # Test multiple noise types on same measurement + NoiseModel() + .add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0])) + .add_noise(PhaseFlip(0.2), MeasureCriteria(qubits=[0])), + Circuit().x(0).measure(0), + Circuit().x(0).bit_flip(0, 0.1).phase_flip(0, 0.2).measure(0), + ), + ], +) +def test_measure_criteria_in_noise_model(noise_model, input_circuit, expected_circuit): + noisy_circuit = noise_model.apply(input_circuit) + assert noisy_circuit == expected_circuit + + +def test_measure_criteria_from_dict(): + # Test with no qubits + d = {"__class__": "MeasureCriteria", "qubits": None} + obj = MeasureCriteria.from_dict(d) + assert isinstance(obj, MeasureCriteria) + assert obj._qubits is None + + # Test with specific qubits + d = {"__class__": "MeasureCriteria", "qubits": [0, 1]} + obj = MeasureCriteria.from_dict(d) + assert isinstance(obj, MeasureCriteria) + assert obj._qubits == {0, 1} + + +def test_measure_criteria_from_dict_via_class_and_base(): + d = {"__class__": "MeasureCriteria", "qubits": [0, 1]} + # Call via MeasureCriteria + obj1 = MeasureCriteria.from_dict(d) + assert isinstance(obj1, MeasureCriteria) + assert obj1._qubits == {0, 1} + + # Call via base class CircuitInstructionCriteria + from braket.circuits.noise_model.circuit_instruction_criteria import CircuitInstructionCriteria + + obj2 = CircuitInstructionCriteria.from_dict(d) + assert isinstance(obj2, MeasureCriteria) + assert obj2._qubits == {0, 1} + + # Call via Criteria base class + from braket.circuits.noise_model.criteria import Criteria + + obj3 = Criteria.from_dict(d) + assert isinstance(obj3, MeasureCriteria) + assert obj3._qubits == {0, 1} diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py index c68ca7cf4..1bc646400 100644 --- a/test/unit_tests/braket/circuits/noise/test_noise_model.py +++ b/test/unit_tests/braket/circuits/noise/test_noise_model.py @@ -25,8 +25,12 @@ ObservableCriteria, QubitInitializationCriteria, UnitaryGateCriteria, + CriteriaKey, + MeasureCriteria, ) from braket.circuits.noises import BitFlip, Depolarizing, PauliChannel, TwoQubitDepolarizing +from braket.circuits.measure import Measure +from braket.circuits.result_types import Sample, Expectation def h_unitary(): @@ -488,3 +492,85 @@ def test_apply_to_circuit_list(): with pytest.raises(TypeError): noise_model.add_noise(Mock(), Mock(spec=Criteria)) noise_model.apply([]) + + +def test_noise_model_from_dict(): + noise_dict = { + "instructions": [ + { + "noise": {"__class__": "BitFlip", "probability": 0.1}, + "criteria": {"__class__": "GateCriteria", "qubits": [0, 1], "gates": ["H"]}, + } + ] + } + model = NoiseModel.from_dict(noise_dict) + assert len(model.instructions) == 1 + assert isinstance(model.instructions[0].noise, BitFlip) + assert model.instructions[0].noise.probability == 0.1 + assert isinstance(model.instructions[0].criteria, GateCriteria) + assert model.instructions[0].criteria.get_keys(CriteriaKey.QUBIT) == {0, 1} + + +def test_apply_readout_noise_measure_only(): + circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + + noise = BitFlip(0.1) + noise_model = NoiseModel().add_noise(noise, MeasureCriteria(qubits=[0])) + + noisy_circuit = noise_model.apply(circuit) + + # Check that noise is applied after measure(0) + found_measure_noise = False + for instr in noisy_circuit.instructions: + if isinstance(instr.operator, BitFlip) and instr.target == [0]: + found_measure_noise = True + assert found_measure_noise + # Also test that measurement on qubit 1 is not affected + for instr in noisy_circuit.instructions: + assert not (isinstance(instr.operator, BitFlip) and instr.target == [1]) + + +def test_apply_readout_noise_measure_only_custom_criteria(): + circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + + class CustomMeasureCriteria(MeasureCriteria): + def instruction_matches(self, instruction): + # Custom logic: only match measurement on qubit 0 + return isinstance(instruction.operator, Measure) and 0 in instruction.target + + noise = BitFlip(0.1) + noise_model = NoiseModel().add_noise(noise, CustomMeasureCriteria(qubits=[0])) + + noisy_circuit = noise_model.apply(circuit) + + # Check that noise is applied after measure(0) + found_measure_noise = False + for instr in noisy_circuit.instructions: + if isinstance(instr.operator, BitFlip) and instr.target == [0]: + found_measure_noise = True + assert found_measure_noise + # Also test that measurement on qubit 1 is not affected + for instr in noisy_circuit.instructions: + assert not (isinstance(instr.operator, BitFlip) and instr.target == [1]) + + +def test_apply_readout_noise_result_type_only(): + circuit = Circuit().h(0).cnot(0, 1) + circuit.add_result_type(Sample(Observable.Z(), 0)) + circuit.add_result_type(Expectation(Observable.Z(), 0)) + + noise = BitFlip(0.1) + noise_model = NoiseModel().add_noise(noise, ObservableCriteria(qubits=[0])) + + # Apply the noise model + noisy_circuit = noise_model.apply(circuit) + + # Check that noise is applied after Sample(0) result type + found_result_type_noise = False + for instr in noisy_circuit.instructions: + if isinstance(instr.operator, BitFlip) and instr.target == [0]: + found_result_type_noise = True + assert found_result_type_noise + # Also test that result types on other qubits are not affected + for instr in noisy_circuit.instructions: + assert not (isinstance(instr.operator, BitFlip) and instr.target == [1]) From 97113c8884fbf5a83500b301a0c8e8886673a850 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 10 Jun 2025 16:20:28 +0000 Subject: [PATCH 289/347] prepare release v1.92.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8898ba1b9..2819caebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.92.0 (2025-06-10) + +### Features + + * Measure Criteria for readout error to target measurement operations + ## v1.91.2 (2025-04-22) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 584ea574f..7c07630c3 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.91.3.dev0" +__version__ = "1.92.0" From 857611def16b0fccc86a73c3aab58d41f5c4ff18 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 10 Jun 2025 16:20:28 +0000 Subject: [PATCH 290/347] update development version to v1.92.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7c07630c3..368229724 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.92.0" +__version__ = "1.92.1.dev0" From 7e97b5373650ef9540f35eec75bd95064bf727be Mon Sep 17 00:00:00 2001 From: Seemanta Bhattacharjee Date: Wed, 11 Jun 2025 01:45:16 +0600 Subject: [PATCH 291/347] feature: add factory methods for AHS AtomArrangements (#1092) Co-authored-by: Peter Komar Co-authored-by: Tim --- examples/ahs_lattice_factory.py | 97 +++++ src/braket/ahs/__init__.py | 1 + src/braket/ahs/atom_arrangement.py | 278 ++++++++++++ src/braket/ahs/canvas.py | 98 +++++ .../test_atom_arrangement_factory_methods.py | 394 ++++++++++++++++++ test/unit_tests/braket/ahs/test_canvas.py | 172 ++++++++ 6 files changed, 1040 insertions(+) create mode 100644 examples/ahs_lattice_factory.py create mode 100644 src/braket/ahs/canvas.py create mode 100644 test/unit_tests/braket/ahs/test_atom_arrangement_factory_methods.py create mode 100644 test/unit_tests/braket/ahs/test_canvas.py diff --git a/examples/ahs_lattice_factory.py b/examples/ahs_lattice_factory.py new file mode 100644 index 000000000..9a34ddea3 --- /dev/null +++ b/examples/ahs_lattice_factory.py @@ -0,0 +1,97 @@ +# 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. + +""" +Demonstration of AHS AtomArrangement factory methods. + +This example shows how to use the new factory methods to create common +lattice arrangements for Analog Hamiltonian Simulation (AHS). +""" + +from braket.ahs import AtomArrangement, Canvas, SiteType + +# Define a canvas (boundary region) where atoms can be placed +canvas_boundary = [ + (0, 0), # Bottom-left corner + (30e-6, 0), # Bottom-right corner + (30e-6, 30e-6), # Top-right corner + (0, 30e-6), # Top-left corner +] +canvas = Canvas(canvas_boundary) + +print("AHS AtomArrangement Factory Methods Demo") +print("=" * 50) + +# 1. Square Lattice +print("\n1. Square Lattice:") +spacing = 5e-6 +square_arrangement = AtomArrangement.from_square_lattice(spacing, canvas) +print(f" Created {len(square_arrangement)} atoms in square lattice") +print(f" Spacing: {spacing * 1e6:.1f} μm") + +# 2. Rectangular Lattice +print("\n2. Rectangular Lattice:") +spacing_x = 4e-6 +spacing_y = 6e-6 +rect_arrangement = AtomArrangement.from_rectangular_lattice(spacing_x, spacing_y, canvas) +print(f" Created {len(rect_arrangement)} atoms in rectangular lattice") +print(f" X spacing: {spacing_x * 1e6:.1f} μm, Y spacing: {spacing_y * 1e6:.1f} μm") + +# 3. Triangular Lattice +print("\n3. Triangular Lattice:") +tri_spacing = 6e-6 +tri_arrangement = AtomArrangement.from_triangular_lattice(tri_spacing, canvas) +print(f" Created {len(tri_arrangement)} atoms in triangular lattice") +print(f" Nearest neighbor distance: {tri_spacing * 1e6:.1f} μm") + +# 4. Honeycomb Lattice +print("\n4. Honeycomb Lattice:") +honey_spacing = 4e-6 +honey_arrangement = AtomArrangement.from_honeycomb_lattice(honey_spacing, canvas) +print(f" Created {len(honey_arrangement)} atoms in honeycomb lattice") +print(f" Nearest neighbor distance: {honey_spacing * 1e6:.1f} μm") + +# 5. Custom Bravais Lattice +print("\n5. Custom Bravais Lattice:") +a1 = (8e-6, 0) +a2 = (4e-6, 7e-6) +custom_arrangement = AtomArrangement.from_bravais_lattice(a1, a2, canvas) +print(f" Created {len(custom_arrangement)} atoms in custom Bravais lattice") +print(f" Lattice vectors: a1={a1}, a2={a2}") + +# 6. Decorated Bravais Lattice +print("\n6. Decorated Bravais Lattice:") +a1 = (10e-6, 0) +a2 = (0, 10e-6) +basis = [(0, 0), (3e-6, 3e-6), (7e-6, 7e-6)] +decorated_arrangement = AtomArrangement.from_bravais_lattice(a1, a2, canvas, basis=basis) +print(f" Created {len(decorated_arrangement)} atoms in decorated lattice") +print(f" Unit cell contains {len(basis)} atoms") + +# 7. Using VACANT sites +print("\n7. Square Lattice with VACANT sites:") +vacant_arrangement = AtomArrangement.from_square_lattice( + spacing=8e-6, canvas=canvas, site_type=SiteType.VACANT +) +print(f" Created {len(vacant_arrangement)} VACANT sites") + +# 8. Complex Canvas Shape (L-shape) +print("\n8. Square Lattice in L-shaped Canvas:") +l_shape_boundary = [(0, 0), (20e-6, 0), (20e-6, 10e-6), (10e-6, 10e-6), (10e-6, 20e-6), (0, 20e-6)] +l_canvas = Canvas(l_shape_boundary) +l_arrangement = AtomArrangement.from_square_lattice(4e-6, l_canvas) +print(f" Created {len(l_arrangement)} atoms in L-shaped region") + +print("\n" + "=" * 50) +print("Demo completed! The factory methods make it easy to create") +print("complex atom arrangements for AHS without manual coordinate calculation.") diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py index 5bf3cc61e..2d902feeb 100644 --- a/src/braket/ahs/__init__.py +++ b/src/braket/ahs/__init__.py @@ -13,6 +13,7 @@ from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation # noqa: F401 from braket.ahs.atom_arrangement import AtomArrangement, AtomArrangementItem, SiteType # noqa: F401 +from braket.ahs.canvas import Canvas # noqa: F401 from braket.ahs.discretization_types import DiscretizationProperties # noqa: F401 from braket.ahs.driving_field import DrivingField # noqa: F401 from braket.ahs.field import Field # noqa: F401 diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py index fc52d3f05..bffac6dc1 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -13,6 +13,7 @@ from __future__ import annotations +import math from collections.abc import Iterator from dataclasses import dataclass from decimal import Decimal @@ -21,6 +22,7 @@ import numpy as np +from braket.ahs.canvas import Canvas from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties @@ -127,3 +129,279 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: raise DiscretizationError(f"Failed to discretize register {e}") from e else: return discretized_arrangement + + @classmethod + def from_square_lattice( + cls, + spacing: Number, + canvas: Canvas, + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Create a square lattice arrangement within a canvas. + + Args: + spacing (Number): Distance between adjacent atoms in meters. + canvas (Canvas): Canvas defining the boundary where atoms can be placed. + site_type (SiteType): The type of sites to create. Default is FILLED. + + Returns: + AtomArrangement: A new atom arrangement with atoms placed in a square lattice. + + Raises: + ValueError: If spacing is not positive. + """ + return cls.from_rectangular_lattice(spacing, spacing, canvas, site_type) + + @classmethod + def from_rectangular_lattice( + cls, + spacing_x: Number, + spacing_y: Number, + canvas: Canvas, + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Create a rectangular lattice arrangement within a canvas. + + Args: + spacing_x (Number): Distance between adjacent atoms in x direction in meters. + spacing_y (Number): Distance between adjacent atoms in y direction in meters. + canvas (Canvas): Canvas defining the boundary where atoms can be placed. + site_type (SiteType): The type of sites to create. Default is FILLED. + + Returns: + AtomArrangement: A new atom arrangement with atoms placed in a rectangular lattice. + + Raises: + ValueError: If either spacing is not positive. + """ + if spacing_x <= 0 or spacing_y <= 0: + raise ValueError("Spacings must be positive") + + # Rectangular lattice vectors + a1 = (spacing_x, 0) + a2 = (0, spacing_y) + + return cls.from_bravais_lattice(a1, a2, canvas, site_type=site_type) + + @classmethod + def from_triangular_lattice( + cls, + spacing: Number, + canvas: Canvas, + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Create a triangular lattice arrangement within a canvas. + + A triangular lattice has the same spacing between all nearest neighbors. + The lattice vectors are (spacing, 0) and (spacing/2, spacing*sqrt(3)/2). + + Args: + spacing (Number): Distance between nearest neighbor atoms in meters. + canvas (Canvas): Canvas defining the boundary where atoms can be placed. + site_type (SiteType): The type of sites to create. Default is FILLED. + + Returns: + AtomArrangement: A new atom arrangement with atoms placed in a triangular lattice. + + Raises: + ValueError: If spacing is not positive. + """ + if spacing <= 0: + raise ValueError("Spacing must be positive") + + # Triangular lattice vectors + a1 = (spacing, 0) + a2 = (spacing / 2, spacing * math.sqrt(3) / 2) + + return cls.from_bravais_lattice(a1, a2, canvas, site_type=site_type) + + @classmethod + def from_honeycomb_lattice( + cls, + spacing: Number, + canvas: Canvas, + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Create a honeycomb lattice arrangement within a canvas. + + A honeycomb lattice is a triangular Bravais lattice with a two-atom basis. + The spacing parameter refers to the nearest neighbor distance. + + Args: + spacing (Number): Distance between nearest neighbor atoms in meters. + canvas (Canvas): Canvas defining the boundary where atoms can be placed. + site_type (SiteType): The type of sites to create. Default is FILLED. + + Returns: + AtomArrangement: A new atom arrangement with atoms placed in a honeycomb lattice. + + Raises: + ValueError: If spacing is not positive. + """ + if spacing <= 0: + raise ValueError("Spacing must be positive") + + # Honeycomb is triangular lattice with 2-atom basis + # Lattice parameter is sqrt(3) times the nearest neighbor distance + lattice_spacing = spacing * math.sqrt(3) + + # Triangular lattice vectors + a1 = (lattice_spacing, 0) + a2 = (lattice_spacing / 2, lattice_spacing * math.sqrt(3) / 2) + + # Two-atom basis + basis = [(0, 0), (a1[0] / 3 + a2[0] / 3, a1[1] / 3 + a2[1] / 3)] + + return cls.from_bravais_lattice(a1, a2, canvas, basis=basis, site_type=site_type) + + @classmethod + def _calculate_lattice_bounds( + cls, + a1: tuple[Number, Number], + a2: tuple[Number, Number], + canvas_bounds: tuple[tuple[Number, Number], tuple[Number, Number]], + basis: list[tuple[Number, Number]] | None = None, + ) -> tuple[tuple[int, int], tuple[int, int]]: + """Calculate the range of lattice indices needed to cover canvas bounds. + + Uses linear algebra to find the minimal bounding box in lattice coordinates + that covers the entire canvas bounding box, accounting for basis offsets. + + Args: + a1: First lattice vector (x, y) + a2: Second lattice vector (x, y) + canvas_bounds: ((min_x, min_y), (max_x, max_y)) + basis: List of basis vectors to account for in bounds calculation + + Returns: + ((n1_min, n1_max), (n2_min, n2_max)): Range of lattice indices + """ + if basis is None: + basis = [(0, 0)] + + (min_x, min_y), (max_x, max_y) = canvas_bounds + + # Cache float conversions for performance + a1_x, a1_y = float(a1[0]), float(a1[1]) + a2_x, a2_y = float(a2[0]), float(a2[1]) + + # Calculate determinant with error checking + det = a1_x * a2_y - a1_y * a2_x + if abs(det) < 1e-12: + raise ValueError(f"Lattice vectors are too close to parallel (det={det})") + + # Inverse matrix: [[a2_y, -a2_x], [-a1_y, a1_x]] / det + inv_00, inv_01 = a2_y / det, -a2_x / det + inv_10, inv_11 = -a1_y / det, a1_x / det + + # Canvas corners in real space, expanded with basis offsets + base_corners = [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)] + corners = list(base_corners) # Start with base corners + + # Add corners shifted by each basis vector (both positive and negative) + for basis_x, basis_y in basis: + if basis_x != 0 or basis_y != 0: # Skip origin basis + basis_x_f, basis_y_f = float(basis_x), float(basis_y) + corners.extend([ + (corner_x + basis_x_f, corner_y + basis_y_f) + for corner_x, corner_y in base_corners + ]) + corners.extend([ + (corner_x - basis_x_f, corner_y - basis_y_f) + for corner_x, corner_y in base_corners + ]) + + # Transform all points to lattice coordinates + n1_values = [] + n2_values = [] + + for x, y in corners: + n1 = inv_00 * x + inv_01 * y + n2 = inv_10 * x + inv_11 * y + n1_values.append(n1) + n2_values.append(n2) + + # margin calculation for lattice bounds + lattice_magnitudes = [math.sqrt(a1_x**2 + a1_y**2), math.sqrt(a2_x**2 + a2_y**2)] + min_lattice_mag = min(lattice_magnitudes) + + if basis: + max_basis_mag = max(math.sqrt(float(bx) ** 2 + float(by) ** 2) for bx, by in basis) + margin = max(2, math.ceil(max_basis_mag / min_lattice_mag) + 1) + else: + margin = 2 + + # Convert to integer bounds + n1_min = math.floor(min(n1_values)) - margin + n1_max = math.ceil(max(n1_values)) + margin + n2_min = math.floor(min(n2_values)) - margin + n2_max = math.ceil(max(n2_values)) + margin + + return (n1_min, n1_max), (n2_min, n2_max) + + @classmethod + def from_bravais_lattice( + cls, + a1: tuple[Number, Number], + a2: tuple[Number, Number], + canvas: Canvas, + basis: list[tuple[Number, Number]] | None = None, + site_type: SiteType = SiteType.FILLED, + ) -> AtomArrangement: + """Create a general Bravais lattice arrangement within a canvas. + + Args: + a1 (tuple[Number, Number]): First lattice vector (x, y) in meters. + a2 (tuple[Number, Number]): Second lattice vector (x, y) in meters. + canvas (Canvas): Canvas defining the boundary where atoms can be placed. + basis (list[tuple[Number, Number]], optional): Basis vectors for decorated lattice. + If None, uses a single atom at (0, 0). Default is None. + site_type (SiteType): The type of sites to create. Default is FILLED. + + Returns: + AtomArrangement: A new atom arrangement with atoms placed in a Bravais lattice. + + Raises: + ValueError: If lattice vectors are parallel or zero. + """ + # Validate lattice vectors + if a1 == (0, 0) or a2 == (0, 0): + raise ValueError("Lattice vectors cannot be zero") + + # Check if vectors are parallel (cross product should be non-zero) + cross_product = a1[0] * a2[1] - a1[1] * a2[0] + if abs(cross_product) < 1e-12: + raise ValueError("Lattice vectors cannot be parallel") + + if basis is None: + basis = [(0, 0)] + + arrangement = cls() + (min_x, min_y), (max_x, max_y) = canvas.get_bounding_box() + + # Calculate lattice bounds using linear algebra approach + (n1_min, n1_max), (n2_min, n2_max) = cls._calculate_lattice_bounds( + a1, a2, ((min_x, min_y), (max_x, max_y)), basis + ) + + # Cache float conversions + a1_x, a1_y = float(a1[0]), float(a1[1]) + a2_x, a2_y = float(a2[0]), float(a2[1]) + basis_float = [(float(bx), float(by)) for bx, by in basis] + + # Generate lattice points + for n1 in range(n1_min, n1_max + 1): + for n2 in range(n2_min, n2_max + 1): + # Calculate lattice site position + lattice_x = n1 * a1_x + n2 * a2_x + lattice_y = n1 * a1_y + n2 * a2_y + + # Add all basis atoms at this lattice site + for basis_x, basis_y in basis_float: + point = (lattice_x + basis_x, lattice_y + basis_y) + + # Check if point is within canvas + if canvas.contains_point(point): + arrangement.add(point, site_type) + + return arrangement diff --git a/src/braket/ahs/canvas.py b/src/braket/ahs/canvas.py new file mode 100644 index 000000000..90950d398 --- /dev/null +++ b/src/braket/ahs/canvas.py @@ -0,0 +1,98 @@ +# 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. + +from __future__ import annotations + +from numbers import Number + + +class Canvas: + """Defines a region where atoms can be placed using boundary points. + + A Canvas represents a polygonal region in 2D space defined by boundary points. + It is used by factory methods to determine which lattice sites should contain atoms. + """ + + def __init__(self, boundary_points: list[tuple[Number, Number]]): + """Initialize a Canvas with boundary points. + + Args: + boundary_points (list[tuple[Number, Number]]): List of (x, y) coordinates + defining the polygon boundary. Must have at least 3 points. + + Raises: + ValueError: If fewer than 3 boundary points are provided. + TypeError: If boundary points are not properly formatted. + """ + self._validate_boundary_points(boundary_points) + self.boundary_points = boundary_points + + def _validate_boundary_points(self, boundary_points: list[tuple[Number, Number]]) -> None: + """Validate the boundary points.""" + if len(boundary_points) < 3: + raise ValueError("Canvas must have at least 3 boundary points") + + for i, point in enumerate(boundary_points): + if not isinstance(point, (tuple, list)) or len(point) != 2: + raise TypeError(f"Boundary point {i} must be a tuple/list of length 2") + + for j, coord in enumerate(point): + if not isinstance(coord, Number): + raise TypeError( + f"Coordinate {j} of boundary point {i} must be a number, got {type(coord)}" + ) + + def contains_point(self, point: tuple[Number, Number]) -> bool: + """Check if a point is inside the canvas using ray casting algorithm. + + Args: + point (tuple[Number, Number]): The (x, y) coordinate to test. + + Returns: + bool: True if the point is inside the canvas, False otherwise. + """ + x, y = point + n = len(self.boundary_points) + inside = False + + p1x, p1y = self.boundary_points[0] + for i in range(1, n + 1): + p2x, p2y = self.boundary_points[i % n] + + # Skip horizontal edges + if p1y == p2y: + p1x, p1y = p2x, p2y + continue + + if y > min(p1y, p2y) and y <= max(p1y, p2y) and x <= max(p1x, p2x): + xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x + if p1x == p2x or x <= xinters: + inside = not inside + p1x, p1y = p2x, p2y + + return inside + + def get_bounding_box(self) -> tuple[tuple[Number, Number], tuple[Number, Number]]: + """Get the bounding box of the canvas. + + Returns: + tuple[tuple[Number, Number], tuple[Number, Number]]: + ((min_x, min_y), (max_x, max_y)) coordinates of the bounding box. + """ + x_coords = [point[0] for point in self.boundary_points] + y_coords = [point[1] for point in self.boundary_points] + + min_x, max_x = min(x_coords), max(x_coords) + min_y, max_y = min(y_coords), max(y_coords) + + return ((min_x, min_y), (max_x, max_y)) diff --git a/test/unit_tests/braket/ahs/test_atom_arrangement_factory_methods.py b/test/unit_tests/braket/ahs/test_atom_arrangement_factory_methods.py new file mode 100644 index 000000000..5d14612fc --- /dev/null +++ b/test/unit_tests/braket/ahs/test_atom_arrangement_factory_methods.py @@ -0,0 +1,394 @@ +# 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. + +import math +from decimal import Decimal +import itertools + +import pytest + +from braket.ahs.atom_arrangement import AtomArrangement, SiteType +from braket.ahs.canvas import Canvas + + +class TestAtomArrangementFactoryMethods: + @pytest.fixture + def square_canvas(self): + """A simple square canvas for testing.""" + return Canvas([(0, 0), (1e-5, 0), (1e-5, 1e-5), (0, 1e-5)]) + + @pytest.fixture + def rectangular_canvas(self): + """A rectangular canvas for testing.""" + return Canvas([(0, 0), (2e-5, 0), (2e-5, 1e-5), (0, 1e-5)]) + + @pytest.fixture + def triangular_canvas(self): + """A triangular canvas for testing.""" + return Canvas([(0, 0), (1e-5, 0), (0.5e-5, 1e-5)]) + + def test_from_square_lattice_basic(self, square_canvas): + """Test basic square lattice creation.""" + spacing = 2e-6 + arrangement = AtomArrangement.from_square_lattice(spacing, square_canvas) + + # Canvas is 1e-5 x 1e-5, with spacing 2e-6 + assert len(arrangement) == 25 # Expected 5x5 grid (excluding boundary points) + + # Check some specific positions that should definitely be present + coordinates = [site.coordinate for site in arrangement] + assert (2e-6, 2e-6) in coordinates # Interior point should be present + assert (4e-6, 4e-6) in coordinates # Another interior point + + # Check that we have the expected lattice structure + x_coords = sorted(set(coord[0] for coord in coordinates)) + y_coords = sorted(set(coord[1] for coord in coordinates)) + + # Should have regularly spaced coordinates + assert len(x_coords) == 5 # 5 distinct x coordinates + assert len(y_coords) == 5 # 5 distinct y coordinates + + # Check spacing + if len(x_coords) > 1: + assert abs(x_coords[1] - x_coords[0] - spacing) < 1e-10 + if len(y_coords) > 1: + assert abs(y_coords[1] - y_coords[0] - spacing) < 1e-10 + + def test_from_square_lattice_site_type(self, square_canvas): + """Test square lattice with specific site type.""" + spacing = 5e-6 + arrangement = AtomArrangement.from_square_lattice( + spacing, square_canvas, site_type=SiteType.VACANT + ) + + # All sites should be VACANT + for site in arrangement: + assert site.site_type == SiteType.VACANT + + def test_from_square_lattice_invalid_spacing(self, square_canvas): + """Test square lattice with invalid spacing.""" + with pytest.raises(ValueError, match="Spacings must be positive"): + AtomArrangement.from_square_lattice(0, square_canvas) + + with pytest.raises(ValueError, match="Spacings must be positive"): + AtomArrangement.from_square_lattice(-1e-6, square_canvas) + + @pytest.mark.parametrize( + "spacing_x,spacing_y,expected_count,test_coords", + [ + (4e-6, 2e-6, 25, [(4e-6, 2e-6), (8e-6, 4e-6), (1.2e-05, 6e-6), (1.6e-05, 8e-6)]), + (5e-6, 5e-6, 8, [(5e-6, 5e-6), (1e-05, 1e-05), (2e-05, 5e-6), (2e-05, 1e-05)]), + (10e-6, 5e-6, 4, [(1e-05, 5e-6), (1e-05, 1e-05), (2e-05, 5e-6), (2e-05, 1e-05)]), + ], + ) + def test_from_rectangular_lattice_basic( + self, rectangular_canvas, spacing_x, spacing_y, expected_count, test_coords + ): + """Test basic rectangular lattice creation with various spacings.""" + arrangement = AtomArrangement.from_rectangular_lattice( + spacing_x, spacing_y, rectangular_canvas + ) + + assert len(arrangement) == expected_count + + coordinates = [site.coordinate for site in arrangement] + for coord in test_coords: + # Check if coordinate exists with floating point tolerance + found = any( + abs(actual[0] - coord[0]) < 1e-10 and abs(actual[1] - coord[1]) < 1e-10 + for actual in coordinates + ) + assert found, f"Expected coordinate {coord} not found in {coordinates}" + + # Verify spacing pattern + x_coords = sorted(set(coord[0] for coord in coordinates)) + y_coords = sorted(set(coord[1] for coord in coordinates)) + + if len(x_coords) > 1: + for i in range(1, len(x_coords)): + spacing_diff = abs(x_coords[i] - x_coords[i - 1] - spacing_x) + assert spacing_diff < 1e-10, f"X spacing mismatch: {spacing_diff}" + + if len(y_coords) > 1: + for i in range(1, len(y_coords)): + spacing_diff = abs(y_coords[i] - y_coords[i - 1] - spacing_y) + assert spacing_diff < 1e-10, f"Y spacing mismatch: {spacing_diff}" + + def test_from_rectangular_lattice_invalid_spacing(self, rectangular_canvas): + """Test rectangular lattice with invalid spacing.""" + with pytest.raises(ValueError, match="Spacings must be positive"): + AtomArrangement.from_rectangular_lattice(0, 1e-6, rectangular_canvas) + + with pytest.raises(ValueError, match="Spacings must be positive"): + AtomArrangement.from_rectangular_lattice(1e-6, -1e-6, rectangular_canvas) + + def test_from_triangular_lattice_basic(self, square_canvas): + """Test basic triangular lattice creation.""" + spacing = 3e-6 + arrangement = AtomArrangement.from_triangular_lattice(spacing, square_canvas) + + # Canvas is 1e-5 x 1e-5, with spacing 3e-6 + assert len(arrangement) == 9 + + def test_from_triangular_lattice_invalid_spacing(self, square_canvas): + """Test triangular lattice with invalid spacing.""" + with pytest.raises(ValueError, match="Spacing must be positive"): + AtomArrangement.from_triangular_lattice(-1e-6, square_canvas) + + def test_from_honeycomb_lattice_basic(self, square_canvas): + """Test basic honeycomb lattice creation.""" + spacing = 2e-6 + arrangement = AtomArrangement.from_honeycomb_lattice(spacing, square_canvas) + + # Canvas is 1e-5 x 1e-5, with spacing 2e-6 (nearest neighbor distance) + # Should produce exactly 18 atoms in honeycomb arrangement + assert len(arrangement) == 18 + + def test_from_honeycomb_lattice_invalid_spacing(self, square_canvas): + """Test honeycomb lattice with invalid spacing.""" + with pytest.raises(ValueError, match="Spacing must be positive"): + AtomArrangement.from_honeycomb_lattice(0, square_canvas) + + def test_from_bravais_lattice_basic(self, square_canvas): + """Test basic Bravais lattice creation.""" + a1 = (3e-6, 0) + a2 = (0, 3e-6) + arrangement = AtomArrangement.from_bravais_lattice(a1, a2, square_canvas) + + # Canvas is 1e-5 x 1e-5, with lattice vectors (3e-6, 0) and (0, 3e-6) + # Should produce exactly 9 atoms in square lattice arrangement + assert len(arrangement) == 9 + + # Check that expected lattice position exists + coordinates = [site.coordinate for site in arrangement] + assert (3e-6, 3e-6) in coordinates # Interior point should be present + + def test_from_bravais_lattice_with_basis(self, square_canvas): + """Test Bravais lattice with custom basis.""" + a1 = (4e-6, 0) + a2 = (0, 4e-6) + basis = [(1e-6, 1e-6), (2e-6, 2e-6)] # Two-atom basis + + arrangement = AtomArrangement.from_bravais_lattice(a1, a2, square_canvas, basis=basis) + + # Canvas is 1e-5 x 1e-5, with lattice vectors (4e-6, 0) and (0, 4e-6) + # Two-atom basis should produce exactly 18 atoms (9 lattice sites × 2 basis atoms) + assert len(arrangement) == 18 + + def test_from_bravais_lattice_invalid_vectors(self, square_canvas): + """Test Bravais lattice with invalid lattice vectors.""" + # Zero vector + with pytest.raises(ValueError, match="Lattice vectors cannot be zero"): + AtomArrangement.from_bravais_lattice((0, 0), (1e-6, 0), square_canvas) + + # Parallel vectors + with pytest.raises(ValueError, match="Lattice vectors cannot be parallel"): + AtomArrangement.from_bravais_lattice((1e-6, 0), (2e-6, 0), square_canvas) + + def test_from_bravais_lattice_with_decimals(self, square_canvas): + """Test Bravais lattice with Decimal coordinates.""" + a1 = (Decimal("3e-6"), Decimal("0")) + a2 = (Decimal("0"), Decimal("3e-6")) + + arrangement = AtomArrangement.from_bravais_lattice(a1, a2, square_canvas) + + # Should produce same result as float version (9 atoms) + assert len(arrangement) == 9 + + def test_triangular_lattice_spacing_geometry(self): + """Test that triangular lattice has correct geometric properties.""" + # Use a larger canvas to get multiple lattice points + canvas = Canvas([(1e-6, 1e-6), (1e-4, 1e-6), (1e-4, 1e-4), (1e-6, 1e-4)]) + spacing = 1e-5 + + arrangement = AtomArrangement.from_triangular_lattice(spacing, canvas) + + # Large canvas (99μm × 99μm) with spacing 10μm should produce exactly 108 atoms + assert len(arrangement) == 108 + + def test_honeycomb_lattice_structure(self): + """Test that honeycomb lattice has correct structure.""" + # Use a larger canvas with interior region + canvas = Canvas([(1e-6, 1e-6), (2e-4, 1e-6), (2e-4, 2e-4), (1e-6, 2e-4)]) + spacing = 1e-5 # Nearest neighbor distance + + arrangement = AtomArrangement.from_honeycomb_lattice(spacing, canvas) + + # Large canvas (199μm × 199μm) with spacing 10μm should produce exactly 311 atoms + assert len(arrangement) == 311 + + def test_honeycomb_lattice_contains_requested_spacing(self, square_canvas): + """At least one pair of atoms must be separated by exactly `spacing` + (the declared nearest-neighbour distance).""" + spacing = 2e-6 + arr = AtomArrangement.from_honeycomb_lattice(spacing, square_canvas) + coords = [site.coordinate for site in arr] + + # Should have exactly 18 atoms for this canvas and spacing + assert len(coords) == 18 + + # collect unique pairwise distances (rounded to tame fp noise) + distances = { + round(math.hypot(x1 - x2, y1 - y2), 12) + for (x1, y1), (x2, y2) in itertools.combinations(coords, 2) + } + + # there should be at least one distance equal to `spacing` + assert any(math.isclose(d, spacing, rel_tol=1e-7, abs_tol=1e-12) for d in distances), ( + "No pair found at the requested nearest-neighbour distance" + ) + + def test_honeycomb_lattice_min_distance_not_smaller_than_spacing(self, square_canvas): + """The shortest non-zero distance in the honeycomb lattice must be + at least the user-requested `spacing`. A too-small value signals an + incorrect basis.""" + spacing = 2e-6 + arr = AtomArrangement.from_honeycomb_lattice(spacing, square_canvas) + coords = [site.coordinate for site in arr] + + # Should have exactly 18 atoms for this canvas and spacing + assert len(coords) == 18 + + min_dist = min( + math.hypot(x1 - x2, y1 - y2) + for (x1, y1), (x2, y2) in itertools.combinations(coords, 2) + if (x1, y1) != (x2, y2) + ) + + # Allow tiny numerical wiggle room + assert min_dist >= spacing * 0.999, ( + f"Shortest distance {min_dist:e} is < requested spacing {spacing:e};" + ) + + def test_factory_methods_empty_canvas(self): + """Test factory methods with canvas that contains no lattice points.""" + # Very small canvas that might not contain any lattice points + tiny_canvas = Canvas([(1e-7, 1e-7), (2e-7, 1e-7), (2e-7, 2e-7), (1e-7, 2e-7)]) + large_spacing = 1e-5 + + # Canvas is too small for the spacing, should produce no atoms + arrangement = AtomArrangement.from_square_lattice(large_spacing, tiny_canvas) + assert len(arrangement) == 0 + + def test_mixed_site_types_with_factory_methods(self, square_canvas): + """Test that factory methods respect site_type parameter.""" + spacing = 3e-6 + + filled_arrangement = AtomArrangement.from_square_lattice( + spacing, square_canvas, site_type=SiteType.FILLED + ) + vacant_arrangement = AtomArrangement.from_square_lattice( + spacing, square_canvas, site_type=SiteType.VACANT + ) + + # Same number of sites + assert len(filled_arrangement) == len(vacant_arrangement) + + # But different site types + for site in filled_arrangement: + assert site.site_type == SiteType.FILLED + + for site in vacant_arrangement: + assert site.site_type == SiteType.VACANT + + def test_lattice_methods_with_complex_canvas(self): + """Test lattice methods with a more complex canvas shape.""" + # L-shaped canvas + l_canvas = Canvas([ + (0, 0), + (1e-5, 0), + (1e-5, 0.5e-5), + (0.5e-5, 0.5e-5), + (0.5e-5, 1e-5), + (0, 1e-5), + ]) + + spacing = 2e-6 + arrangement = AtomArrangement.from_square_lattice(spacing, l_canvas) + + # L-shaped canvas with spacing 2e-6 should produce exactly 16 atoms + assert len(arrangement) == 16 + + # Verify all atoms are within the canvas + for site in arrangement: + assert l_canvas.contains_point(site.coordinate) + + def test_from_bravais_lattice_default_basis(self, square_canvas): + """Test Bravais lattice with default basis (None), which should use [(0, 0)].""" + a1 = (3e-6, 0) + a2 = (0, 3e-6) + + # Call with explicit basis=None to trigger the default basis path + arrangement = AtomArrangement.from_bravais_lattice(a1, a2, square_canvas, basis=None) + + # Should produce same result as no basis specified (9 atoms) + assert len(arrangement) == 9 + + def test_calculate_lattice_bounds_direct_nearly_parallel(self): + """Test _calculate_lattice_bounds directly with nearly parallel vectors.""" + # Test the _calculate_lattice_bounds method directly + a1 = (1e-6, 1e-20) # Very small y component + a2 = (1e-6, 2e-20) # Even smaller y component difference + canvas_bounds = ((0, 0), (1e-5, 1e-5)) + + # The determinant will be: 1e-6 * 2e-20 - 1e-20 * 1e-6 = 2e-26 - 1e-26 = 1e-26 < 1e-12 + with pytest.raises(ValueError, match="Lattice vectors are too close to parallel"): + AtomArrangement._calculate_lattice_bounds(a1, a2, canvas_bounds) + + def test_calculate_lattice_bounds_default_basis(self): + """Test _calculate_lattice_bounds with basis=None to trigger default basis path.""" + # Test the _calculate_lattice_bounds method directly + a1 = (3e-6, 0) + a2 = (0, 3e-6) + canvas_bounds = ((0, 0), (1e-5, 1e-5)) + + # Call with basis=None to trigger the default basis = [(0, 0)] + bounds = AtomArrangement._calculate_lattice_bounds(a1, a2, canvas_bounds, basis=None) + + assert isinstance(bounds, tuple) + assert len(bounds) == 2 + (n1_min, n1_max), (n2_min, n2_max) = bounds + assert isinstance(n1_min, int) and isinstance(n1_max, int) + assert isinstance(n2_min, int) and isinstance(n2_max, int) + + def test_calculate_lattice_bounds_empty_basis(self): + """Test _calculate_lattice_bounds with empty basis to trigger margin=2 path.""" + # Test the _calculate_lattice_bounds method directly + a1 = (3e-6, 0) + a2 = (0, 3e-6) + canvas_bounds = ((0, 0), (1e-5, 1e-5)) + + # Call with empty basis to trigger the margin = 2 + bounds = AtomArrangement._calculate_lattice_bounds(a1, a2, canvas_bounds, basis=[]) + + assert isinstance(bounds, tuple) + assert len(bounds) == 2 + (n1_min, n1_max), (n2_min, n2_max) = bounds + assert isinstance(n1_min, int) and isinstance(n1_max, int) + assert isinstance(n2_min, int) and isinstance(n2_max, int) + + def test_atom_arrangement_item_validation_errors(self): + """Test AtomArrangementItem validation error paths.""" + from braket.ahs.atom_arrangement import AtomArrangementItem, SiteType + + # Test invalid coordinate length + with pytest.raises(ValueError, match="must be of length 2"): + AtomArrangementItem((1, 2, 3), SiteType.FILLED) + + # Test invalid coordinate type + with pytest.raises(TypeError, match="must be a number"): + AtomArrangementItem(("invalid", 2), SiteType.FILLED) + + # Test invalid site type + with pytest.raises(ValueError, match="must be one of"): + AtomArrangementItem((1, 2), "invalid_site_type") diff --git a/test/unit_tests/braket/ahs/test_canvas.py b/test/unit_tests/braket/ahs/test_canvas.py new file mode 100644 index 000000000..519d5198a --- /dev/null +++ b/test/unit_tests/braket/ahs/test_canvas.py @@ -0,0 +1,172 @@ +# 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. + +from decimal import Decimal + +import pytest + +from braket.ahs.canvas import Canvas + + +class TestCanvas: + def test_init_valid_square(self): + """Test Canvas initialization with a valid square.""" + boundary_points = [(0, 0), (1, 0), (1, 1), (0, 1)] + canvas = Canvas(boundary_points) + assert canvas.boundary_points == boundary_points + + def test_init_valid_triangle(self): + """Test Canvas initialization with a valid triangle.""" + boundary_points = [(0, 0), (1, 0), (0.5, 1)] + canvas = Canvas(boundary_points) + assert canvas.boundary_points == boundary_points + + def test_init_invalid_too_few_points(self): + """Test Canvas initialization with too few points.""" + with pytest.raises(ValueError, match="Canvas must have at least 3 boundary points"): + Canvas([(0, 0), (1, 0)]) + + def test_init_invalid_point_format(self): + """Test Canvas initialization with invalid point format.""" + with pytest.raises(TypeError, match="Boundary point 1 must be a tuple/list of length 2"): + Canvas([(0, 0), (1,), (0.5, 1)]) + + def test_init_invalid_coordinate_type(self): + """Test Canvas initialization with invalid coordinate type.""" + with pytest.raises(TypeError, match="Coordinate 0 of boundary point 1 must be a number"): + Canvas([(0, 0), ("invalid", 0), (0.5, 1)]) + + def test_init_with_decimals(self): + """Test Canvas initialization with Decimal coordinates.""" + boundary_points = [ + (Decimal("0"), Decimal("0")), + (Decimal("1"), Decimal("0")), + (Decimal("0.5"), Decimal("1")), + ] + canvas = Canvas(boundary_points) + assert canvas.boundary_points == boundary_points + + def test_contains_point_square_inside(self): + """Test point containment for points inside a square.""" + canvas = Canvas([(0, 0), (2, 0), (2, 2), (0, 2)]) + + # Points clearly inside + assert canvas.contains_point((1, 1)) + assert canvas.contains_point((0.5, 0.5)) + assert canvas.contains_point((1.5, 1.5)) + + def test_contains_point_square_outside(self): + """Test point containment for points outside a square.""" + canvas = Canvas([(0, 0), (2, 0), (2, 2), (0, 2)]) + + # Points clearly outside + assert not canvas.contains_point((-1, 1)) + assert not canvas.contains_point((3, 1)) + assert not canvas.contains_point((1, -1)) + assert not canvas.contains_point((1, 3)) + + def test_contains_point_square_on_boundary(self): + """Test point containment for points on boundary of a square.""" + canvas = Canvas([(0, 0), (2, 0), (2, 2), (0, 2)]) + + # Points on boundary (ray casting may vary for boundary points) + # The exact behavior on boundary is implementation-dependent + # but should be consistent + boundary_result = canvas.contains_point((0, 1)) + assert isinstance(boundary_result, bool) + + def test_contains_point_triangle(self): + """Test point containment for a triangle.""" + canvas = Canvas([(0, 0), (2, 0), (1, 2)]) + + # Point inside triangle + assert canvas.contains_point((1, 0.5)) + + # Points outside triangle + assert not canvas.contains_point((-1, 0)) + assert not canvas.contains_point((3, 0)) + assert not canvas.contains_point((1, 3)) + + def test_contains_point_complex_polygon(self): + """Test point containment for a more complex polygon.""" + # L-shaped polygon + canvas = Canvas([(0, 0), (2, 0), (2, 1), (1, 1), (1, 2), (0, 2)]) + + # Points inside L-shape + assert canvas.contains_point((0.5, 0.5)) + assert canvas.contains_point((1.5, 0.5)) + assert canvas.contains_point((0.5, 1.5)) + + # Point in the "missing" part of the L + assert not canvas.contains_point((1.5, 1.5)) + + def test_get_bounding_box_square(self): + """Test bounding box calculation for a square.""" + canvas = Canvas([(1, 1), (3, 1), (3, 3), (1, 3)]) + (min_x, min_y), (max_x, max_y) = canvas.get_bounding_box() + + assert min_x == 1 + assert min_y == 1 + assert max_x == 3 + assert max_y == 3 + + def test_get_bounding_box_triangle(self): + """Test bounding box calculation for a triangle.""" + canvas = Canvas([(-1, 0), (2, 0), (0.5, 3)]) + (min_x, min_y), (max_x, max_y) = canvas.get_bounding_box() + + assert min_x == -1 + assert min_y == 0 + assert max_x == 2 + assert max_y == 3 + + def test_get_bounding_box_with_decimals(self): + """Test bounding box calculation with Decimal coordinates.""" + canvas = Canvas([ + (Decimal("-1.5"), Decimal("0.5")), + (Decimal("2.5"), Decimal("0.5")), + (Decimal("0.5"), Decimal("3.5")), + ]) + (min_x, min_y), (max_x, max_y) = canvas.get_bounding_box() + + assert min_x == Decimal("-1.5") + assert min_y == Decimal("0.5") + assert max_x == Decimal("2.5") + assert max_y == Decimal("3.5") + + def test_edge_cases_degenerate_triangle(self): + """Test edge case with degenerate triangle (collinear points).""" + # Points are collinear (degenerate triangle) + canvas = Canvas([(0, 0), (1, 0), (2, 0)]) + + # Point "inside" the degenerate triangle + result = canvas.contains_point((1, 0)) + assert isinstance(result, bool) # Should not crash + + def test_edge_cases_duplicate_points(self): + """Test edge case with duplicate boundary points.""" + canvas = Canvas([(0, 0), (1, 0), (1, 0), (0, 1)]) + + # Should not crash and should work reasonably + result = canvas.contains_point((0.3, 0.3)) + assert isinstance(result, bool) + + def test_vertical_edge_ray_casting(self): + """Test vertical edge case to ensure complete coverage.""" + canvas = Canvas([(0, 0), (1, 0), (1, 1), (0, 1)]) + + # Test point that will cause ray casting to interact with vertical edge + assert canvas.contains_point((0.5, 0.5)) + + # Test point outside that interacts with vertical edge + assert not canvas.contains_point((-0.5, 0.5)) From af202aa049f9d83e15b18986c7c109bb6f540dd0 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 11 Jun 2025 16:13:37 +0000 Subject: [PATCH 292/347] prepare release v1.93.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2819caebe..e3bd85906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.93.0 (2025-06-11) + +### Features + + * add factory methods for AHS AtomArrangements + ## v1.92.0 (2025-06-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 368229724..63c983f2f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.92.1.dev0" +__version__ = "1.93.0" From 47aad1fc6cde991dea6c33ddc5634a86e3e588fe Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 11 Jun 2025 16:13:37 +0000 Subject: [PATCH 293/347] update development version to v1.93.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 63c983f2f..170b4dbb7 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.93.0" +__version__ = "1.93.1.dev0" From 6046a3453ca456233604501090bddf1ddb309fa0 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 26 Jun 2025 12:40:29 -0400 Subject: [PATCH 294/347] feat: support IQM dynamic circuit capabilities (#1099) --- .../text_circuit_diagram_utils.py | 8 +- .../experimental_capabilities/__init__.py | 17 ++ .../experimental_capability_context.py | 99 +++++++ .../iqm/classical_control.py | 261 ++++++++++++++++++ .../experimental_capabilities/conftest.py | 23 ++ .../iqm/test_iqm_experimental_capabilities.py | 169 ++++++++++++ .../test_experimental_capabilities.py | 44 +++ 7 files changed, 617 insertions(+), 4 deletions(-) create mode 100644 src/braket/experimental_capabilities/__init__.py create mode 100644 src/braket/experimental_capabilities/experimental_capability_context.py create mode 100644 src/braket/experimental_capabilities/iqm/classical_control.py create mode 100644 test/unit_tests/braket/experimental_capabilities/conftest.py create mode 100644 test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py create mode 100644 test/unit_tests/braket/experimental_capabilities/test_experimental_capabilities.py diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 51b41ceb7..0bddd4c36 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -19,9 +19,8 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction -from braket.circuits.measure import Measure from braket.circuits.moments import MomentType -from braket.circuits.noise import Noise +from braket.circuits.quantum_operator import QuantumOperator from braket.circuits.result_type import ResultType from braket.registers.qubit_set import QubitSet @@ -129,9 +128,10 @@ def _group_items( """ groupings = [] for item in items: - # Can only print Gate, Noise and Measure operators for instructions at the moment + # Can only print QuantumOperator and CompilerDirective operators for instructions at + # the moment if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective, Measure) + item.operator, (CompilerDirective, QuantumOperator) ): continue diff --git a/src/braket/experimental_capabilities/__init__.py b/src/braket/experimental_capabilities/__init__.py new file mode 100644 index 000000000..b1fc12107 --- /dev/null +++ b/src/braket/experimental_capabilities/__init__.py @@ -0,0 +1,17 @@ +# 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. + +from braket.experimental_capabilities.experimental_capability_context import ( + EnableExperimentalCapability, # noqa: F401 +) +from braket.experimental_capabilities.iqm import classical_control # noqa: F401 diff --git a/src/braket/experimental_capabilities/experimental_capability_context.py b/src/braket/experimental_capabilities/experimental_capability_context.py new file mode 100644 index 000000000..44e88be02 --- /dev/null +++ b/src/braket/experimental_capabilities/experimental_capability_context.py @@ -0,0 +1,99 @@ +# 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. + +from __future__ import annotations + +import types + + +class ExperimentalCapabilityContextError(Exception): + """Exception raised for errors related to experimental capability contexts.""" + + +class GlobalExperimentalCapabilityContext: + def __init__(self) -> None: + """A global singleton that tracks whether experimental capabilities are enabled.""" + self._is_enabled = False + + @property + def is_enabled(self) -> bool: + """Check if experimental capabilities are enabled. + + Returns: + bool: True if experimental capabilities are enabled, False otherwise. + """ + return self._is_enabled + + def enable(self) -> None: + """Enable all experimental capabilities.""" + self._is_enabled = True + + def disable(self) -> None: + """Disable all experimental capabilities.""" + self._is_enabled = False + + def set_state(self, state: bool) -> None: + """Set the state of all experimental capabilities. + + Args: + state: The state to set. True to enable all capabilities, False to disable all. + """ + self._is_enabled = bool(state) + + +# Global singleton instance +GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT = GlobalExperimentalCapabilityContext() + + +class EnableExperimentalCapability: + def __init__(self) -> None: + """This context manager temporarily enables experimental capabilities + for the duration of a code block, after which the capabilities are + returned to their previous states. + + Experimental capabilities are those that hardware providers are rapidly + developing. As hardware improve and the support on the capabilities are expanded, + the behavior may change, including becoming less restrictive, more performant + and having higher quality. See the developer guide [1] to learn more about + experimental capabilities on Amazon Braket. + [1] https://docs.aws.amazon.com/braket/latest/developerguide/ + braket-experimental-capabilities.html + + Examples: + >>> with EnableExperimentalCapability(): + ... circuit = Circuit() + ... circuit.cc_prx(0, 0.1, 0.2, 0) + + """ + self._previous_state = GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + def __enter__(self) -> None: + """Enter the context, enabling all specified capabilities. + This method saves the current state of each capability and then enables them. + """ + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.enable() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + """Exit the context, restoring each capability to its previous state. + + Args: + exc_type: The exception type if an exception was raised in the context, else None. + exc_val: The exception value if an exception was raised in the context, else None. + exc_tb: The exception traceback if an exception was raised in the context, else None. + """ + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.set_state(self._previous_state) diff --git a/src/braket/experimental_capabilities/iqm/classical_control.py b/src/braket/experimental_capabilities/iqm/classical_control.py new file mode 100644 index 000000000..0bfd4318f --- /dev/null +++ b/src/braket/experimental_capabilities/iqm/classical_control.py @@ -0,0 +1,261 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any, Optional + +from braket.circuits import circuit +from braket.circuits.angled_gate import _multi_angled_ascii_characters +from braket.circuits.free_parameter_expression import FreeParameterExpression +from braket.circuits.instruction import Instruction +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.serialization import ( + IRType, + SerializationProperties, +) + +# from braket.experimental_capabilities.experimental_capability import ExperimentalCapability +from braket.experimental_capabilities.experimental_capability_context import ( + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT, + ExperimentalCapabilityContextError, +) + +# from braket.experimental_capabilities.iqm.iqm_experimental_capabilities import ( +# IqmExperimentalCapabilities, +# ) +from braket.registers.qubit_set import QubitSet, QubitSetInput + +# EXPCAP_FLAG = IqmExperimentalCapabilities.classical_control.value + + +class ExperimentalQuantumOperator(QuantumOperator): + def __init__( + self, + qubit_count: int, + ascii_symbols: list[str], + ) -> None: + """Base class for experimental quantum operators. + + This class provides the foundation for quantum operators that are part of + experimental capabilities and ensures that they can only be instantiated + when the appropriate capability is enabled. + + Args: + expcap_flag: The experimental capability flag that must be + enabled to use this operator. + qubit_count: The number of qubits this operator acts on. + ascii_symbols: ASCII string symbols for the operator. + """ + self._validate_experimental_capabilities_enabled() + + super().__init__(qubit_count=qubit_count, ascii_symbols=ascii_symbols) + self._parameters = None + + @property + def _qasm_name(self) -> str: + """Get the OpenQASM name for this operator. + + This property must be implemented by subclasses. + + Returns: + str: The OpenQASM name for this operator. + + Raises: + NotImplementedError: If the subclass does not implement this property. + """ + raise NotImplementedError + + @property + def ascii_symbols(self) -> tuple[str, ...]: + """Get the ASCII symbols for this operator. + + Returns: + tuple[str]: The ASCII symbols for this operator. + """ + return self._ascii_symbols + + @property + def parameters(self) -> list[FreeParameterExpression | float | int]: + """Get the parameters associated with this operator. + + Returns: + list[FreeParameterExpression | float | int]: The free parameters or fixed values + associated with this operator. + """ + return self._parameters + + def to_ir( + self, + target: Optional[QubitSet] = None, + ir_type: IRType = IRType.OPENQASM, + serialization_properties: Optional[SerializationProperties] = None, + **kwargs: Any, + ) -> Any: + """Convert this operator to its IR representation. + + Args: + target: Target qubit(s). Defaults to None. + ir_type: The IR type to use. Defaults to IRType.OPENQASM. + serialization_properties: Properties to use for serialization. Defaults to None. + + Returns: + Any: The IR representation of this operator. + """ + if ir_type != IRType.OPENQASM: + raise ValueError(f"supplied ir_type {ir_type} is not supported for {self._qasm_name}.") + + target_qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] + parameters = f"({', '.join(map(str, self.parameters))})" + return f"{self._qasm_name}{parameters} {target_qubits[0]};" + + def _validate_experimental_capabilities_enabled(self) -> None: + """Check if the experimental capabilities are enabled.""" + if not GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled: + raise ExperimentalCapabilityContextError( + f"{self.__class__.__name__} is an experimental capability. It can only be " + "instantiated under EnableExperimentalCapability. For more information about " + "experimental capabilities, view the Amazon Braket Developer Guide." + ) + + +class CCPRx(ExperimentalQuantumOperator): + """Classically controlled Phased Rx gate. + + A rotation around the X-axis with a phase factor, where the rotation depends + on the value of a classical feedback. + + Args: + angle_1 (FreeParameterExpression | float): The first angle of the gate in radians or + expression representation. + angle_2 (FreeParameterExpression | float): The second angle of the gate in radians or + expression representation. + feedback_key (int): The integer feedback key that points to a measurement result. + """ + + def __init__( + self, + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + feedback_key: int, + ): + ascii_symbols = f"{feedback_key}→" + _multi_angled_ascii_characters( + "CCPRx", angle_1, angle_2 + ) + super().__init__(qubit_count=1, ascii_symbols=[ascii_symbols]) + + angles = [ + (angle if isinstance(angle, FreeParameterExpression) else float(angle)) + for angle in (angle_1, angle_2) + ] + self._parameters = [*angles, feedback_key] + + @property + def _qasm_name(self) -> str: + """Get the OpenQASM name for this operator. + + Returns: + str: The OpenQASM name "cc_prx". + """ + return "cc_prx" + + @staticmethod + @circuit.subroutine(register=True) + def cc_prx( + target: QubitSetInput, + angle_1: FreeParameterExpression | float, + angle_2: FreeParameterExpression | float, + feedback_key: int, + ) -> Iterable[Instruction]: + """Conditional PhaseRx gate. + + Applies a rotation around the X-axis with a phase factor, conditioned on + the value in a classical feedback register. + + Args: + target (QubitSetInput): Target qubit(s). + angle_1 (FreeParameterExpression | float): First angle in radians. + angle_2 (FreeParameterExpression | float): Second angle in radians. + feedback_key (int): The integer feedback key that points to a measurement result. + + Returns: + Iterable[Instruction]: CCPRx instruction. + + Examples: + >>> circ = Circuit().cc_prx(0, 0.15, 0.25, 0) + """ + return [ + Instruction( + CCPRx(angle_1, angle_2, feedback_key), + target=qubit, + ) + for qubit in QubitSet(target) + ] + + +class MeasureFF(ExperimentalQuantumOperator): + r"""Measurement for Feed Forward control. + + Performs a measurement. The result is associated with a feedback key that + can be used later in conditional operations. + + Args: + feedback_key (int): The integer feedback key that points to a measurement result. + """ + + def __init__( + self, + feedback_key: int, + ) -> None: + ascii_symbols = f"MFF→{feedback_key}" + super().__init__(qubit_count=1, ascii_symbols=[ascii_symbols]) + self._parameters = [feedback_key] + + @property + def _qasm_name(self) -> str: + """Get the OpenQASM name for this operator. + + Returns: + str: The OpenQASM name "measure_ff". + """ + return "measure_ff" + + @staticmethod + @circuit.subroutine(register=True) + def measure_ff( + target: QubitSetInput, + feedback_key: int, + ) -> Iterable[Instruction]: + r"""Measure and store result for feed-forward operations. + + Performs a measurement on the target qubit and stores the result in a + classical feedback register for later use in conditional operations. + + Args: + target (QubitSetInput): Target qubit(s). + feedback_key (int): The integer feedback key that points to a measurement result. + + Returns: + Iterable[Instruction]: MeasureFF instruction. + + Examples: + >>> circ = Circuit().measure_ff(0, 0) + """ + return [ + Instruction( + MeasureFF(feedback_key), + target=qubit, + ) + for qubit in QubitSet(target) + ] diff --git a/test/unit_tests/braket/experimental_capabilities/conftest.py b/test/unit_tests/braket/experimental_capabilities/conftest.py new file mode 100644 index 000000000..be0f06bbe --- /dev/null +++ b/test/unit_tests/braket/experimental_capabilities/conftest.py @@ -0,0 +1,23 @@ +# 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. + +import pytest + +from braket.experimental_capabilities.experimental_capability_context import ( + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT, +) + + +@pytest.fixture(autouse=True) +def reset_capabilities_context(): + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.disable() diff --git a/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py new file mode 100644 index 000000000..f5f3b5108 --- /dev/null +++ b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py @@ -0,0 +1,169 @@ +# 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. + +from __future__ import annotations + +import math +from unittest.mock import MagicMock, patch + +import pytest + +from braket.circuits import Circuit +from braket.circuits.free_parameter import FreeParameter +from braket.circuits.serialization import IRType, OpenQASMSerializationProperties +from braket.experimental_capabilities import ( + EnableExperimentalCapability, +) +from braket.experimental_capabilities.experimental_capability_context import ( + ExperimentalCapabilityContextError, +) +from braket.experimental_capabilities.iqm.classical_control import CCPRx, MeasureFF + + +def test_ccprx_invalid_capability_context(): + # Without enabling the capability, CCPRx should raise an error + with pytest.raises(ExperimentalCapabilityContextError): + CCPRx(0.1, 0.2, 0) + + +def test_ccprx_with_capability(): + # With capability enabled, CCPRx should work + with EnableExperimentalCapability(): + cc_prx = CCPRx(0.5, 0.8, 0) + assert cc_prx.parameters == [0.5, 0.8, 0] + assert cc_prx.ascii_symbols == ("0→CCPRx(0.50, 0.80)",) + + circuit = Circuit() + circuit.cc_prx(0, math.pi / 2, math.pi / 4, 0) + assert len(circuit.instructions) == 1 + assert isinstance(circuit.instructions[0].operator, CCPRx) + + +def test_ccprx_with_free_parameters(): + with EnableExperimentalCapability(): + # Create CCPRx with FreeParameter + theta = FreeParameter("theta") + phi = FreeParameter("phi") + cc_prx = CCPRx(theta, phi, 0) + assert cc_prx.parameters == [theta, phi, 0] + + circuit = Circuit() + circuit.cc_prx(0, theta, phi, 0) + assert circuit.instructions[0].operator.parameters[0] == theta + assert circuit.instructions[0].operator.parameters[1] == phi + + +def test_measure_ff_invalid_capability_context(): + # Without enabling the capability, MeasureFF should raise an error + with pytest.raises(ExperimentalCapabilityContextError): + MeasureFF(0) + + +def test_measure_ff_with_capability(): + # With capability enabled, MeasureFF should work + with EnableExperimentalCapability(): + measure_ff = MeasureFF(0) + assert measure_ff.parameters == [0] + assert measure_ff.ascii_symbols == ("MFF→0",) + + circuit = Circuit() + circuit.measure_ff(0, 0) + assert len(circuit.instructions) == 1 + assert isinstance(circuit.instructions[0].operator, MeasureFF) + + +def test_measure_ff_properties(): + with EnableExperimentalCapability(): + measure_ff = MeasureFF(0) + + +def test_ccprx_to_ir(): + with EnableExperimentalCapability(): + cc_prx = CCPRx(math.pi / 2, math.pi / 4, 0) + target = [0] + serialization_props = OpenQASMSerializationProperties(qubit_reference_type="indexed") + + ir = cc_prx.to_ir( + target=target, ir_type=IRType.OPENQASM, serialization_properties=serialization_props + ) + + assert ir == "cc_prx(1.5707963267948966, 0.7853981633974483, 0) $0;" + + +def test_measure_ff_to_ir(): + with EnableExperimentalCapability(): + measure_ff = MeasureFF(0) + target = [0] + serialization_props = OpenQASMSerializationProperties(qubit_reference_type="indexed") + + ir = measure_ff.to_ir( + target=target, ir_type=IRType.OPENQASM, serialization_properties=serialization_props + ) + + assert ir == "measure_ff(0) $0;" + + +def test_unsupported_ir_type(): + with EnableExperimentalCapability(): + cc_prx = CCPRx(math.pi / 2, math.pi / 4, 0) + target = [0] + + with pytest.raises(ValueError): + cc_prx.to_ir(target=target, ir_type=IRType.JAQCD) + + +def test_mixing_standard_and_experimental_operations(): + with EnableExperimentalCapability(): + circuit = Circuit() + circuit.h(0) + circuit.cc_prx(0, math.pi / 2, math.pi / 4, 0) + circuit.cnot(0, 1) + circuit.measure_ff(0, 1) + + assert len(circuit.instructions) == 4 + + +@patch("braket.aws.AwsDevice") +def test_circuit_with_classical_control_e2e(mock_aws_device_class): + expected_measurement_counts = {"0": 46, "1": 54} + + mock_device = MagicMock() + mock_task = MagicMock() + mock_result = MagicMock() + mock_result.measurement_counts = expected_measurement_counts + mock_task.result.return_value = mock_result + mock_device.run.return_value = mock_task + mock_aws_device_class.return_value = mock_device + + device = mock_aws_device_class("arn:aws:braket:us-west-2::device/qpu/iqm/Garnet") + + with EnableExperimentalCapability(): + circuit = Circuit() + circuit.cc_prx(1, math.pi / 2, math.pi / 2, 0) + circuit.measure_ff(1, 0) + + circuit = Circuit().add_verbatim_box(circuit) + result = device.run(circuit, shots=100).result() + + device.run.assert_called_once() + assert device.run.call_args[0][0] == circuit + assert device.run.call_args[1]["shots"] == 100 + assert result.measurement_counts == expected_measurement_counts + + +def test_example_expected_to_fail(): + circuit = Circuit() + + with pytest.raises(Exception): + circuit.cc_prx(1, math.pi / 2, math.pi / 2, 0) + circuit.measure_ff(1, 0) diff --git a/test/unit_tests/braket/experimental_capabilities/test_experimental_capabilities.py b/test/unit_tests/braket/experimental_capabilities/test_experimental_capabilities.py new file mode 100644 index 000000000..24711e4a4 --- /dev/null +++ b/test/unit_tests/braket/experimental_capabilities/test_experimental_capabilities.py @@ -0,0 +1,44 @@ +# 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. + +from __future__ import annotations + +from braket.experimental_capabilities import EnableExperimentalCapability +from braket.experimental_capabilities.experimental_capability_context import ( + GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT, +) + + +def test_enable_experimental_capability_context(): + assert not GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + with EnableExperimentalCapability(): + assert GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + assert not GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + +def test_nested_capability_contexts(): + # Test that nested contexts work correctly + with EnableExperimentalCapability(): + assert GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + # Nested context - should preserve state on exit + with EnableExperimentalCapability(): + assert GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + # After inner context, the capability should still be enabled + assert GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled + + # After nested context, the capability should be disabled + assert not GLOBAL_EXPERIMENTAL_CAPABILITY_CONTEXT.is_enabled From 33b0854b733410d41c45d2b10fa380f997d56389 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 26 Jun 2025 17:02:21 +0000 Subject: [PATCH 295/347] prepare release v1.94.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3bd85906..dda79a3f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.94.0 (2025-06-26) + +### Features + + * support IQM dynamic circuit capabilities + ## v1.93.0 (2025-06-11) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 170b4dbb7..cb47b80e0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.93.1.dev0" +__version__ = "1.94.0" From 1b71ac8eaa5729f8bbed38d7cf100ff5b60b9f7a Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 26 Jun 2025 17:02:21 +0000 Subject: [PATCH 296/347] update development version to v1.94.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index cb47b80e0..02db34773 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.94.0" +__version__ = "1.94.1.dev0" From c03deac2cbd529eb22402bfbc7dad1b2cb4dd7b2 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Tue, 1 Jul 2025 10:12:42 -0400 Subject: [PATCH 297/347] feat: support from_ir for cc_prx and measure_ff (#1100) --- src/braket/circuits/translations.py | 3 ++ .../iqm/test_iqm_experimental_capabilities.py | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 3cefa4d89..726b1c3a9 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -32,6 +32,7 @@ import braket.circuits.gates as braket_gates import braket.circuits.result_types as ResultTypes # noqa: N812 from braket.circuits import Observable, noises, observables +from braket.experimental_capabilities.iqm.classical_control import CCPRx, MeasureFF BRAKET_GATES = { "gphase": braket_gates.GPhase, @@ -74,6 +75,8 @@ "prx": braket_gates.PRx, "ms": braket_gates.MS, "unitary": braket_gates.Unitary, + "cc_prx": CCPRx, + "measure_ff": MeasureFF, } one_prob_noise_map = { diff --git a/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py index f5f3b5108..a2880fa2b 100644 --- a/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py +++ b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py @@ -28,6 +28,8 @@ ExperimentalCapabilityContextError, ) from braket.experimental_capabilities.iqm.classical_control import CCPRx, MeasureFF +from braket.ir.openqasm import Program as OpenQasmProgram +import numpy as np def test_ccprx_invalid_capability_context(): @@ -167,3 +169,38 @@ def test_example_expected_to_fail(): with pytest.raises(Exception): circuit.cc_prx(1, math.pi / 2, math.pi / 2, 0) circuit.measure_ff(1, 0) + + +def test_measureff_ccprx_from_ir(): + """Test that circuits with both standard and experimental gates can be reconstructed from IR.""" + with EnableExperimentalCapability(): + openqasm_source = """ + OPENQASM 3.0; + bit[2] b; + #pragma braket verbatim + box{ + prx(3.141592653589793, 0.0) $1; + measure_ff(0) $1; + cc_prx(3.141592653589793, 0.0, 0) $1; + } + """ + ir = OpenQasmProgram(source=openqasm_source) + circuit_from_ir = Circuit.from_ir(ir) + + assert len(circuit_from_ir.instructions) == 3 + + assert circuit_from_ir.instructions[0].operator.name == "PRx" + assert isinstance(circuit_from_ir.instructions[1].operator, MeasureFF) + assert isinstance(circuit_from_ir.instructions[2].operator, CCPRx) + + instruction = circuit_from_ir.instructions[1] + params = instruction.operator.parameters + assert params[0] == 0 + assert instruction.target == [1] + + instruction = circuit_from_ir.instructions[2] + params = instruction.operator.parameters + assert np.isclose(params[0], 3.141592653589793) + assert np.isclose(params[1], 0) + assert params[2] == 0 + assert instruction.target == [1] From 4d0b176aeff86c883d842f8482493fc635c45481 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Jul 2025 16:19:06 +0000 Subject: [PATCH 298/347] prepare release v1.95.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dda79a3f1..1c2155791 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.95.0 (2025-07-01) + +### Features + + * support from_ir for cc_prx and measure_ff + ## v1.94.0 (2025-06-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 02db34773..468094a8b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.94.1.dev0" +__version__ = "1.95.0" From 5a7a4db48893a7d83198ce31884ae053e2ad32fa Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 1 Jul 2025 16:19:06 +0000 Subject: [PATCH 299/347] update development version to v1.95.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 468094a8b..a36287bf7 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.95.0" +__version__ = "1.95.1.dev0" From e1ffcf05d0496a8747acc91bbfed19cc49fd77c1 Mon Sep 17 00:00:00 2001 From: Jacob Feldman Date: Thu, 17 Jul 2025 13:38:27 -0700 Subject: [PATCH 300/347] feat: Add Emerald (#1103) --- src/braket/devices/devices.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/braket/devices/devices.py b/src/braket/devices/devices.py index 2cad66674..64ab3ccdc 100644 --- a/src/braket/devices/devices.py +++ b/src/braket/devices/devices.py @@ -29,6 +29,7 @@ class _DWave(str, Enum): class _IQM(str, Enum): Garnet = "arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet" + Emerald = "arn:aws:braket:eu-north-1::device/qpu/iqm/Emerald" class _IonQ(str, Enum): _Harmony = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" From 2ffbfba8a76a3026810b3574b05bdb17f5522135 Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:01:47 -0700 Subject: [PATCH 301/347] fix: positional param issue for shots in tests (#1104) Co-authored-by: Viraj Chaudhari <> --- test/integ_tests/test_measure.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py index 15e46bd3e..373395538 100644 --- a/test/integ_tests/test_measure.py +++ b/test/integ_tests/test_measure.py @@ -48,7 +48,7 @@ def test_unsupported_devices(arn): def test_measure_on_local_sim(sim): circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) device = LocalSimulator(sim) - result = device.run(circ, SHOTS).result() + result = device.run(circ, shots=SHOTS).result() assert len(result.measurements[0]) == 2 assert result.measured_qubits == [0, 1] @@ -59,7 +59,7 @@ def test_measure_on_supported_devices(arn): if not device.is_available: pytest.skip("Device offline") circ = Circuit().h(0).cnot(0, 1).measure([0]) - result = device.run(circ, SHOTS).result() + result = device.run(circ, shots=SHOTS).result() assert len(result.measurements[0]) == 1 assert result.measured_qubits == [0] @@ -72,7 +72,7 @@ def test_measure_on_supported_devices(arn): ], ) def test_measure_targets(circuit, expected_measured_qubits): - result = DEVICE.run(circuit, SHOTS).result() + result = DEVICE.run(circuit, shots=SHOTS).result() assert result.measured_qubits == expected_measured_qubits assert len(result.measurements[0]) == len(expected_measured_qubits) @@ -80,6 +80,6 @@ def test_measure_targets(circuit, expected_measured_qubits): def test_measure_with_noise(): device = LocalSimulator("braket_dm") circuit = Circuit().x(0).x(1).bit_flip(0, probability=0.1).measure(0) - result = device.run(circuit, SHOTS).result() + result = device.run(circuit, shots=SHOTS).result() assert result.measured_qubits == [0] assert len(result.measurements[0]) == 1 From b6a4c7db70a2987294c9703fe47cdfc86bc40a53 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Jul 2025 22:15:12 +0000 Subject: [PATCH 302/347] prepare release v1.96.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c2155791..a8f4311a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.96.0 (2025-07-17) + +### Features + + * Add Emerald + +### Bug Fixes and Other Changes + + * positional param issue for shots in tests + ## v1.95.0 (2025-07-01) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a36287bf7..afe9881ca 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.95.1.dev0" +__version__ = "1.96.0" From b2d671c1c5a1b58a075e2e8a066c00d63f9f3f54 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 17 Jul 2025 22:15:12 +0000 Subject: [PATCH 303/347] update development version to v1.96.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index afe9881ca..26d9ffe09 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.96.0" +__version__ = "1.96.1.dev0" From 13db50fe9c9925602148b83d01f44ffe0b620576 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Mon, 21 Jul 2025 17:46:14 -0400 Subject: [PATCH 304/347] infra: update to latest oqpy version (#1102) Co-authored-by: Coull --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d57ddde28..5fc6b3d39 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ install_requires=[ "amazon-braket-schemas>=1.23.0", "amazon-braket-default-simulator>=1.26.0", - "oqpy~=0.3.5", + "oqpy~=0.3.7", "backoff", "boltons", "boto3>=1.28.53", From eddeef3d0e573a665162abe5c342730fa246f1e4 Mon Sep 17 00:00:00 2001 From: maolinml <86260930+maolinml@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:40:56 -0700 Subject: [PATCH 305/347] fix: Fix applying readout error with ObservableResultType and MeasureCriteria (#1105) Co-authored-by: Tim (Yi-Ting) --- src/braket/circuits/angled_gate.py | 2 +- src/braket/circuits/noise_model/noise_model.py | 6 +++++- .../unit_tests/braket/circuits/noise/test_noise_model.py | 9 +++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 35f73f984..ee2f30423 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -367,7 +367,7 @@ def _angles_equal( @_angles_equal.register -def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): # noqa: FURB118 +def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): return angle_1 == angle_2 diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 46c1b6215..8aa635575 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -25,6 +25,7 @@ from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult from braket.circuits.noise_model.initialization_criteria import InitializationCriteria from braket.circuits.noise_model.measure_criteria import MeasureCriteria +from braket.circuits.noise_model.observable_criteria import ObservableCriteria from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria from braket.circuits.result_types import ObservableResultType from braket.registers.qubit_set import QubitSetInput @@ -421,7 +422,10 @@ def _apply_noise_on_observable_result_types( if isinstance(result_type, ObservableResultType): target_qubits = list(result_type.target) for item_index, item in enumerate(readout_noise_instructions): - if item.criteria.result_type_matches(result_type): + item_criteria = item.criteria + if isinstance( + item_criteria, ObservableCriteria + ) and item_criteria.result_type_matches(result_type): for target_qubit in target_qubits: noise_to_apply[target_qubit].add(item_index) for qubit in noise_to_apply: diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py index 1bc646400..d184aeb6b 100644 --- a/test/unit_tests/braket/circuits/noise/test_noise_model.py +++ b/test/unit_tests/braket/circuits/noise/test_noise_model.py @@ -574,3 +574,12 @@ def test_apply_readout_noise_result_type_only(): # Also test that result types on other qubits are not affected for instr in noisy_circuit.instructions: assert not (isinstance(instr.operator, BitFlip) and instr.target == [1]) + +def test_measurecriteria_for_circuit_with_observable_resulttype(): + noise_model = NoiseModel() + noise_model.add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0])) + + circ = Circuit().h(0).sample(observable=Observable.Z()) + noisy_circuit = noise_model.apply(circ) + + assert noisy_circuit == circ # no effect \ No newline at end of file From 2425a27683dc52799dc57da6d56986790c3f8c95 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Aug 2025 16:16:32 +0000 Subject: [PATCH 306/347] prepare release v1.96.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f4311a7..ee9ab86d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.96.1 (2025-08-05) + +### Bug Fixes and Other Changes + + * Fix applying readout error with ObservableResultType and MeasureCriteria + ## v1.96.0 (2025-07-17) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 26d9ffe09..f53c82726 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.96.1.dev0" +__version__ = "1.96.1" From 286d3e4e76dfc7af0c22fa3e9f8697db8100297a Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 5 Aug 2025 16:16:32 +0000 Subject: [PATCH 307/347] update development version to v1.96.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f53c82726..5c8ef83d0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.96.1" +__version__ = "1.96.2.dev0" From ac2ddf01e0a7d354f41a54b7fcbd3d9228a279ce Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 13 Aug 2025 10:27:04 -0700 Subject: [PATCH 308/347] feat: Program sets (#1106) --- setup.py | 4 +- src/braket/aws/aws_device.py | 59 +- src/braket/aws/aws_quantum_task.py | 195 ++++--- src/braket/aws/aws_quantum_task_batch.py | 79 +-- src/braket/circuits/circuit.py | 51 +- src/braket/circuits/compiler_directives.py | 8 + src/braket/circuits/gates.py | 4 + src/braket/circuits/observable.py | 35 +- src/braket/circuits/observables.py | 39 +- src/braket/circuits/operator.py | 7 +- src/braket/devices/device.py | 28 +- src/braket/devices/local_simulator.py | 92 ++- src/braket/program_sets/__init__.py | 16 + src/braket/program_sets/circuit_binding.py | 157 +++++ src/braket/program_sets/parameter_sets.py | 167 ++++++ src/braket/program_sets/program_set.py | 289 ++++++++++ src/braket/tasks/__init__.py | 1 + .../tasks/gate_model_quantum_task_result.py | 91 +-- src/braket/tasks/local_quantum_task.py | 19 +- src/braket/tasks/local_quantum_task_batch.py | 29 +- src/braket/tasks/measurement_utils.py | 123 ++++ .../tasks/program_set_quantum_task_result.py | 499 ++++++++++++++++ src/braket/tasks/quantum_task.py | 37 +- .../braket/aws/common_test_utils.py | 114 ++++ test/unit_tests/braket/aws/test_aws_device.py | 25 +- .../braket/aws/test_aws_quantum_task.py | 135 +++++ .../braket/circuits/noise/test_noise_model.py | 3 +- .../braket/circuits/test_circuit.py | 16 +- .../circuits/test_compiler_directives.py | 64 +-- .../braket/circuits/test_observables.py | 459 ++++++++------- .../braket/devices/test_local_simulator.py | 274 ++++++++- .../braket/program_sets/conftest.py | 22 + .../program_sets/program_set_test_utils.py | 26 + .../program_sets/test_circuit_binding.py | 47 ++ .../program_sets/test_parameter_sets.py | 163 ++++++ .../braket/program_sets/test_program_set.py | 536 ++++++++++++++++++ .../test_program_set_quantum_task_result.py | 425 ++++++++++++++ 37 files changed, 3721 insertions(+), 617 deletions(-) create mode 100644 src/braket/program_sets/__init__.py create mode 100644 src/braket/program_sets/circuit_binding.py create mode 100644 src/braket/program_sets/parameter_sets.py create mode 100644 src/braket/program_sets/program_set.py create mode 100644 src/braket/tasks/measurement_utils.py create mode 100644 src/braket/tasks/program_set_quantum_task_result.py create mode 100644 test/unit_tests/braket/program_sets/conftest.py create mode 100644 test/unit_tests/braket/program_sets/program_set_test_utils.py create mode 100644 test/unit_tests/braket/program_sets/test_circuit_binding.py create mode 100644 test/unit_tests/braket/program_sets/test_parameter_sets.py create mode 100644 test/unit_tests/braket/program_sets/test_program_set.py create mode 100644 test/unit_tests/braket/tasks/test_program_set_quantum_task_result.py diff --git a/setup.py b/setup.py index 5fc6b3d39..d01923f11 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.23.0", - "amazon-braket-default-simulator>=1.26.0", + "amazon-braket-schemas>=1.25.0", + "amazon-braket-default-simulator>=1.27.0", "oqpy~=0.3.7", "backoff", "boltons", diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 7672f12e9..126b4b492 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -29,25 +29,24 @@ # TODO: Remove device_action module once this is added to init in the schemas repo from braket.device_schema.pulse.pulse_device_action_properties_v1 import PulseDeviceActionProperties -from braket.ir.blackbird import Program as BlackbirdProgram -from braket.ir.openqasm import Program as OpenQasmProgram +from braket.ir.openqasm import ProgramSet as OpenQASMProgramSet from braket.schema_common import BraketSchemaBase from networkx import DiGraph, complete_graph, from_edgelist -from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation -from braket.annealing.problem import Problem from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch from braket.aws.aws_session import AwsSession from braket.aws.queue_information import QueueDepthInfo, QueueType -from braket.circuits import Circuit, Gate, QubitSet +from braket.circuits import Gate, QubitSet from braket.circuits.gate_calibrations import GateCalibrations from braket.circuits.noise_model import NoiseModel from braket.devices.device import Device from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import _is_float +from braket.program_sets import ProgramSet from braket.pulse import ArbitraryWaveform, Frame, Port, PulseSequence from braket.pulse.waveforms import _parse_waveform_from_calibration_schema +from braket.tasks.quantum_task import TaskSpecification class AwsDeviceType(str, Enum): @@ -67,6 +66,7 @@ class AwsDevice(Device): DEFAULT_SHOTS_QPU = 1000 DEFAULT_SHOTS_SIMULATOR = 0 + DEFAULT_SHOTS_PROGRAM_SET = -1 DEFAULT_MAX_PARALLEL = 10 _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) @@ -121,12 +121,7 @@ def __init__( def run( self, - task_specification: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | PulseSequence - | AnalogHamiltonianSimulation, + task_specification: TaskSpecification, s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, @@ -141,9 +136,9 @@ def run( annealing problem. Args: - task_specification (Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): - Specification of quantum task (circuit, OpenQASM program or AHS program) - to run on device. + task_specification (TaskSpecification): + Specification of quantum task (circuit, OpenQASM program, program set, + pulse sequence or AHS program) to run on the device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the quantum task's results to. Default is `/tasks` if evoked outside a Braket Hybrid Job, `/jobs//tasks` if evoked inside a Braket Hybrid Job. @@ -215,7 +210,7 @@ def run( else None ) or (self._aws_session.default_bucket(), "tasks"), - shots if shots is not None else self._default_shots, + shots if shots is not None else self._default_shots(task_specification), poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds or self._poll_interval_seconds, inputs=inputs, @@ -227,20 +222,7 @@ def run( def run_batch( self, - task_specifications: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | PulseSequence - | AnalogHamiltonianSimulation - | list[ - Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | PulseSequence - | AnalogHamiltonianSimulation - ], + task_specifications: TaskSpecification | list[TaskSpecification], s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, shots: Optional[int] = None, max_parallel: Optional[int] = None, @@ -256,9 +238,9 @@ def run_batch( """Executes a batch of quantum tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation], list[Union[ Circuit, Problem, OpenQasmProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]]]): # noqa - Single instance or list of circuits, annealing problems, pulse sequences, - or photonics program to run on device. + task_specifications (TaskSpecification | list[TaskSpecification]): + Single instance or list of task specifications (circuits, OpenQASM programs, + pulse sequences or AHS programs) to run on the device. s3_destination_folder (Optional[S3DestinationFolder]): The S3 location to save the quantum tasks' results to. Default is `/tasks` if evoked outside a Braket Job, `/jobs//tasks` if evoked inside a Braket Job. @@ -274,7 +256,7 @@ def run_batch( poll_interval_seconds (float): The polling interval for `AwsQuantumTask.result()`, in seconds. Defaults to the ``getTaskPollIntervalMillis`` value specified in ``self.properties.service`` (divided by 1000) if provided, otherwise 1 second. - inputs (Optional[Union[dict[str, float], list[dict[str, float]]]]): Inputs to be + inputs (Optional[dict[str, float] | list[dict[str, float]]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]]): A @@ -309,7 +291,7 @@ def run_batch( else None ) or (self._aws_session.default_bucket(), "tasks"), - shots if shots is not None else self._default_shots, + shots if shots is not None else self._default_shots(), max_parallel=max_parallel if max_parallel is not None else self._default_max_parallel, max_workers=max_connections, poll_timeout_seconds=poll_timeout_seconds, @@ -548,10 +530,13 @@ def _construct_topology_graph(self) -> DiGraph: return from_edgelist(edges, create_using=DiGraph()) return None - @property - def _default_shots(self) -> int: + def _default_shots(self, task_specification: Optional[TaskSpecification] = None) -> int: + if isinstance(task_specification, (ProgramSet, OpenQASMProgramSet)): + return AwsDevice.DEFAULT_SHOTS_PROGRAM_SET return ( - AwsDevice.DEFAULT_SHOTS_QPU if "qpu" in self.arn else AwsDevice.DEFAULT_SHOTS_SIMULATOR + AwsDevice.DEFAULT_SHOTS_QPU + if self._type == AwsDeviceType.QPU + else AwsDevice.DEFAULT_SHOTS_SIMULATOR ) @property diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 66bbc17fa..456bdcad6 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -22,6 +22,7 @@ from typing import Any, ClassVar, Optional, Union import boto3 +from botocore.exceptions import ClientError from braket.device_schema import GateModelParameters from braket.device_schema.dwave import ( Dwave2000QDeviceParameters, @@ -40,23 +41,22 @@ from braket.device_schema.simulators import GateModelSimulatorDeviceParameters from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQASMProgram +from braket.ir.openqasm import ProgramSet as OpenQASMProgramSet from braket.schema_common import BraketSchemaBase from braket.task_result import ( AnalogHamiltonianSimulationTaskResult, AnnealingTaskResult, GateModelTaskResult, PhotonicModelTaskResult, + ProgramSetTaskResult, ) from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.aws.aws_session import AwsSession from braket.aws.queue_information import QuantumTaskQueueInfo, QueueType -from braket.circuits import Instruction from braket.circuits.circuit import Circuit, Gate, QubitSet from braket.circuits.circuit_helpers import validate_circuit_and_shots -from braket.circuits.compiler_directives import StartVerbatimBox -from braket.circuits.gates import PulseGate from braket.circuits.serialization import ( IRType, OpenQASMSerializationProperties, @@ -64,14 +64,17 @@ SerializableProgram, ) from braket.error_mitigation import ErrorMitigation +from braket.program_sets import ProgramSet from braket.pulse.pulse_sequence import PulseSequence from braket.tasks import ( AnalogHamiltonianSimulationQuantumTaskResult, AnnealingQuantumTaskResult, GateModelQuantumTaskResult, PhotonicModelQuantumTaskResult, + ProgramSetQuantumTaskResult, QuantumTask, ) +from braket.tasks.quantum_task import TaskResult, TaskSpecification from braket.tracking.tracking_context import broadcast_event from braket.tracking.tracking_events import _TaskCompletionEvent @@ -94,12 +97,7 @@ class AwsQuantumTask(QuantumTask): def create( aws_session: AwsSession, device_arn: str, - task_specification: Circuit - | Problem - | OpenQASMProgram - | BlackbirdProgram - | PulseSequence - | AnalogHamiltonianSimulation, + task_specification: TaskSpecification, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, device_parameters: Optional[dict[str, Any]] = None, @@ -121,8 +119,7 @@ def create( device_arn (str): The ARN of the quantum device. - task_specification (Union[Circuit, Problem, OpenQASMProgram, BlackbirdProgram, PulseSequence, AnalogHamiltonianSimulation]): # noqa - The specification of the quantum task to run on device. + task_specification (TaskSpecification): Specification of the quantum task to run. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder @@ -175,7 +172,7 @@ def create( See Also: `braket.aws.aws_quantum_simulator.AwsQuantumSimulator.run()` `braket.aws.aws_qpu.AwsQpu.run()` - """ # noqa: E501 + """ if len(s3_destination_folder) != 2: raise ValueError( "s3_destination_folder must be of size 2 with a 'bucket' and 'key' respectively." @@ -217,7 +214,7 @@ def create( device_parameters or {}, disable_qubit_rewiring, inputs, - gate_definitions=gate_definitions, + gate_definitions, quiet=quiet, *args, **kwargs, @@ -231,6 +228,7 @@ def __init__( poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), quiet: bool = False, + task_specification: Optional[TaskSpecification] = None, **kwargs, ): """Initializes an `AwsQuantumTask`. @@ -247,6 +245,8 @@ def __init__( `getLogger(__name__)` quiet (bool): Sets the verbosity of the logger to low and does not report queue position. Default is `False`. + task_specification (Optional[TaskSpecification]): The specification the task + was run with Examples: >>> task = AwsQuantumTask(arn="task_arn") @@ -276,9 +276,8 @@ def __init__( self._quiet = quiet self._metadata: dict[str, Any] = {} - self._result: Optional[ - GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult - ] = None + self._task_specification = task_specification + self._result: TaskResult = None @staticmethod def _aws_session_for_task_arn(task_arn: str) -> AwsSession: @@ -398,9 +397,7 @@ def _update_status_if_nonterminal(self) -> str: cached = self._status(True) return cached if cached in self.TERMINAL_STATES else self._status(metadata_absent) - def result( - self, - ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: + def result(self) -> TaskResult: """Get the quantum task result by polling Amazon Braket to see if the task is completed. Once the quantum task is completed, the result is retrieved from S3 and returned as a `GateModelQuantumTaskResult` or `AnnealingQuantumTaskResult` @@ -410,10 +407,10 @@ def result( Consecutive calls to this method return a cached result from the preceding request. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: The - result of the quantum task, if the quantum task completed successfully; returns - `None` if the quantum task did not complete successfully or the future timed out. - """ # noqa: E501 + TaskResult: The result of the quantum task, if the quantum task completed successfully; + returns `None` if the quantum task did not complete successfully + or the future timed out. + """ if self._result or ( self._metadata and self._status(True) in self.NO_RESULT_TERMINAL_STATES ): @@ -463,20 +460,19 @@ async def _create_future(self) -> asyncio.Task: async def _wait_for_completion( self, - ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: + ) -> TaskResult: """Waits for the quantum task to be completed, then returns the result from the S3 bucket. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: If the task is in the - `AwsQuantumTask.RESULTS_READY_STATES` state within the specified time limit, - the result from the S3 bucket is loaded and returned. - `None` is returned if a timeout occurs or task state is in - `AwsQuantumTask.NO_RESULT_TERMINAL_STATES`. + TaskResult: If the task is in the `AwsQuantumTask.RESULTS_READY_STATES` state + within the specified time limit, the result from the S3 bucket is loaded and returned. + `None` is returned if a timeout occurs or task state is in + `AwsQuantumTask.NO_RESULT_TERMINAL_STATES`. Note: Timeout and sleep intervals are defined in the constructor fields `poll_timeout_seconds` and `poll_interval_seconds` respectively. - """ # noqa: E501 + """ self._logger.debug(f"Task {self._arn}: start polling for completion") start_time = time.time() @@ -520,7 +516,9 @@ def _download_result( current_metadata["outputS3Bucket"], current_metadata["outputS3Directory"] + f"/{AwsQuantumTask.RESULTS_FILENAME}", ) - self._result = _format_result(BraketSchemaBase.parse_raw_schema(result_string)) + self._result = _format_result( + BraketSchemaBase.parse_raw_schema(result_string), self._task_specification + ) task_event = { "arn": self.id, "status": self.state(), @@ -546,11 +544,11 @@ def __hash__(self) -> int: @singledispatch def _create_internal( - task_specification: Circuit | Problem | BlackbirdProgram, + task_specification: TaskSpecification, aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict[str, str], BraketSchemaBase], + device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -565,12 +563,12 @@ def _( pulse_sequence: PulseSequence, aws_session: AwsSession, create_task_kwargs: dict[str, Any], - device_arn: str, + _device_arn: str, # Not currently used for OpenQasmProgram - _device_parameters: Union[dict[str, str], BraketSchemaBase], + _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -593,7 +591,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -649,16 +647,59 @@ def _( ) +@_create_internal.register +def _( + program_set: ProgramSet, + aws_session: AwsSession, + create_task_kwargs: dict[str, Any], + _device_arn: str, + _device_parameters: Union[dict, BraketSchemaBase], + _disable_qubit_rewiring: bool, + _inputs: dict[str, float], + gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + *args, + **kwargs, +): + if create_task_kwargs["shots"] == -1: + if not program_set.shots_per_executable: + raise ValueError("Shots must be specified in program set or during task creation") + create_task_kwargs["shots"] = program_set.total_shots + create_task_kwargs["action"] = program_set.to_ir(gate_definitions=gate_definitions).json() + task_arn = _create_program_set_task(aws_session, **create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, task_specification=program_set, *args, **kwargs) + + +@_create_internal.register +def _( + program_set: OpenQASMProgramSet, + aws_session: AwsSession, + create_task_kwargs: dict[str, Any], + _device_arn: str, + _device_parameters: Union[dict, BraketSchemaBase], + _disable_qubit_rewiring: bool, + _inputs: dict[str, float], + _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + *args, + **kwargs, +): + shots = create_task_kwargs["shots"] + if shots == -1: + raise ValueError("Shots must be specified") + create_task_kwargs["action"] = program_set.json() + task_arn = _create_program_set_task(aws_session, **create_task_kwargs) + return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) + + @_create_internal.register def _( blackbird_program: BlackbirdProgram, aws_session: AwsSession, - create_task_kwargs: dict[str, any], - device_arn: str, + create_task_kwargs: dict[str, Any], + _device_arn: str, _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, - inputs: dict[str, float], - gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], + _inputs: dict[str, float], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -687,23 +728,14 @@ def _( qubitCount=circuit.qubit_count, disableQubitRewiring=disable_qubit_rewiring ) final_device_parameters = ( - _circuit_device_params_from_dict(device_parameters or {}, device_arn, paradigm_parameters) + _circuit_device_params_from_dict(device_parameters, device_arn, paradigm_parameters) if isinstance(device_parameters, dict) else device_parameters ) - - qubit_reference_type = QubitReferenceType.VIRTUAL - - if ( - disable_qubit_rewiring - or Instruction(StartVerbatimBox()) in circuit.instructions - or gate_definitions - or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) - ): - qubit_reference_type = QubitReferenceType.PHYSICAL - - serialization_properties = OpenQASMSerializationProperties( - qubit_reference_type=qubit_reference_type + serialization_properties = ( + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL) + if disable_qubit_rewiring + else None ) openqasm_program = circuit.to_ir( @@ -737,9 +769,9 @@ def _( device_parameters: Union[ dict, DwaveDeviceParameters, DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters ], - _: bool, - inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + _disable_qubit_rewiring: bool, + _inputs: dict[str, float], + _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -758,11 +790,11 @@ def _( analog_hamiltonian_simulation: AnalogHamiltonianSimulation, aws_session: AwsSession, create_task_kwargs: dict[str, Any], - device_arn: str, - device_parameters: dict, - _: AnalogHamiltonianSimulationTaskResult, - inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + _device_arn: str, + _device_parameters: Union[dict, BraketSchemaBase], + _disable_qubit_rewiring: bool, + _inputs: dict[str, float], + _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], *args, **kwargs, ) -> AwsQuantumTask: @@ -800,7 +832,7 @@ def _create_annealing_device_params( device_arn (str): The ARN of the quantum device. Returns: - Union[DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters]: The device parameters. + DwaveAdvantageDeviceParameters |Dwave2000QDeviceParameters: The device parameters. """ if not isinstance(device_params, dict): @@ -841,31 +873,54 @@ def _create_common_params( } +def _create_program_set_task(aws_session: AwsSession, **create_task_kwargs: dict[str, Any]) -> str: + try: + return aws_session.create_quantum_task(**create_task_kwargs) + except ClientError as e: + response = e.response + validation_failures = response.get("programSetValidationFailures") + if not validation_failures: + raise + message = response["message"] + new_message = ( + f"{message} First failure:\n{validation_failures[0]}\n" + "Rerun the task and catch the exception for more details" + ) + response["Error"]["Message"] = new_message + raise ClientError(response, e.operation_name) from e + + @singledispatch def _format_result( - result: GateModelTaskResult | AnnealingTaskResult | PhotonicModelTaskResult, -) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: + result: GateModelTaskResult | ProgramSetTaskResult | AnalogHamiltonianSimulationTaskResult, + task_specification: TaskSpecification, +) -> TaskResult: raise TypeError("Invalid result specification type") @_format_result.register -def _(result: GateModelTaskResult) -> GateModelQuantumTaskResult: +def _(result: GateModelTaskResult, _: TaskSpecification) -> GateModelQuantumTaskResult: GateModelQuantumTaskResult.cast_result_types(result) return GateModelQuantumTaskResult.from_object(result) @_format_result.register -def _(result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: +def _(result: ProgramSetTaskResult, program_set: ProgramSet) -> ProgramSetQuantumTaskResult: + return ProgramSetQuantumTaskResult.from_object(result, program_set=program_set) + + +@_format_result.register +def _(result: AnnealingTaskResult, _: TaskSpecification) -> AnnealingQuantumTaskResult: return AnnealingQuantumTaskResult.from_object(result) @_format_result.register -def _(result: PhotonicModelTaskResult) -> PhotonicModelQuantumTaskResult: +def _(result: PhotonicModelTaskResult, _: TaskSpecification) -> PhotonicModelQuantumTaskResult: return PhotonicModelQuantumTaskResult.from_object(result) @_format_result.register def _( - result: AnalogHamiltonianSimulationTaskResult, + result: AnalogHamiltonianSimulationTaskResult, _: TaskSpecification ) -> AnalogHamiltonianSimulationQuantumTaskResult: return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index d82f1eb89..50f19b675 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -16,7 +16,7 @@ import time from concurrent.futures.thread import ThreadPoolExecutor from itertools import repeat -from typing import TYPE_CHECKING, Any, Optional +from typing import Any, Optional from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram @@ -29,16 +29,9 @@ from braket.circuits.gate import Gate from braket.pulse.pulse_sequence import PulseSequence from braket.registers.qubit_set import QubitSet +from braket.tasks.quantum_task import TaskResult, TaskSpecification from braket.tasks.quantum_task_batch import QuantumTaskBatch -if TYPE_CHECKING: - from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( - AnalogHamiltonianSimulationQuantumTaskResult, - ) - from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult - from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult - from braket.tasks.photonic_model_quantum_task_result import PhotonicModelQuantumTaskResult - class AwsQuantumTaskBatch(QuantumTaskBatch): """Executes a batch of quantum tasks in parallel. @@ -58,14 +51,7 @@ def __init__( self, aws_session: AwsSession, device_arn: str, - task_specifications: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | AnalogHamiltonianSimulation - | list[ - Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation - ], + task_specifications: TaskSpecification | list[TaskSpecification], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, @@ -88,10 +74,8 @@ def __init__( Args: aws_session (AwsSession): AwsSession to connect to AWS with. device_arn (str): The ARN of the quantum device. - task_specifications (Union[Union[Circuit,Problem,OpenQasmProgram,BlackbirdProgram,AnalogHamiltonianSimulation],list[Union[Circuit,Problem,OpenQasmProgram,BlackbirdProgram,AnalogHamiltonianSimulation]]]): # noqa - Single instance or list of circuits, annealing - problems, pulse sequences, or photonics program as specification of quantum task - to run on device. + task_specifications (TaskSpecification | list[TaskSpecification]): + Single instance or list of specifications of quantum tasks to run. s3_destination_folder (AwsSession.S3DestinationFolder): NamedTuple, with bucket for index 0 and key for index 1, that specifies the Amazon S3 bucket and folder to store quantum task results in. @@ -107,10 +91,10 @@ def __init__( in seconds. Default: 5 days. poll_interval_seconds (float): The polling interval for results in seconds. Default: 1 second. - inputs (Union[dict[str, float], list[dict[str, float]]] | None): Inputs to be passed + inputs (dict[str, float] | list[dict[str, float]] | None): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. - gate_definitions (Union[dict[tuple[Gate, QubitSet], PulseSequence], list[dict[tuple[Gate, QubitSet], PulseSequence]]] | None): # noqa: E501 + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | list[dict[tuple[Gate, QubitSet], PulseSequence]] | None): # noqa: E501 User-defined gate calibration. The calibration is defined for a particular `Gate` on a particular `QubitSet` and is represented by a `PulseSequence`. Default: None. reservation_arn (str | None): The reservation ARN provided by Braket Direct @@ -157,22 +141,15 @@ def __init__( @staticmethod def _tasks_inputs_gatedefs( - task_specifications: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | AnalogHamiltonianSimulation - | list[ - Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation - ], + task_specifications: TaskSpecification | list[TaskSpecification], inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, gate_definitions: Optional[ dict[tuple[Gate, QubitSet], PulseSequence] - | list[dict[tuple[Gate, QubitSet], PulseSequence]] + | list[dict[tuple[Gate, QubitSet], PulseSequence]], ] = None, ) -> list[ tuple[ - Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation, + TaskSpecification, dict[str, float], dict[tuple[Gate, QubitSet], PulseSequence], ] @@ -227,14 +204,7 @@ def _tasks_inputs_gatedefs( def _execute( aws_session: AwsSession, device_arn: str, - task_specifications: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | AnalogHamiltonianSimulation - | list[ - Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation - ], + task_specifications: TaskSpecification | list[TaskSpecification], s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, max_parallel: int, @@ -296,11 +266,7 @@ def _create_task( remaining: list[int], aws_session: AwsSession, device_arn: str, - task_specification: Circuit - | Problem - | OpenQasmProgram - | BlackbirdProgram - | AnalogHamiltonianSimulation, + task_specification: TaskSpecification, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, @@ -337,12 +303,7 @@ def results( fail_unsuccessful: bool = False, max_retries: int = MAX_RETRIES, use_cached_value: bool = True, - ) -> list[ - GateModelQuantumTaskResult - | AnnealingQuantumTaskResult - | PhotonicModelQuantumTaskResult - | AnalogHamiltonianSimulationQuantumTaskResult - ]: + ) -> list[TaskResult]: """Retrieves the result of every quantum task in the batch. Polling for results happens in parallel; this method returns when all quantum tasks @@ -359,10 +320,9 @@ def results( even when results have already been cached. Default: `True`. Returns: - list[GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult | AnalogHamiltonianSimulationQuantumTaskResult]: The - results of all of the quantum tasks in the batch. + list[TaskResult]: The results of all of the quantum tasks in the batch. `FAILED`, `CANCELLED`, or timed out quantum tasks will have a result of None - """ # noqa: E501 + """ if not self._results or not use_cached_value: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) self._unsuccessful = { @@ -381,14 +341,7 @@ def results( return self._results @staticmethod - def _retrieve_results( - tasks: list[AwsQuantumTask], max_workers: int - ) -> list[ - GateModelQuantumTaskResult - | AnnealingQuantumTaskResult - | PhotonicModelQuantumTaskResult - | AnalogHamiltonianSimulationQuantumTaskResult - ]: + def _retrieve_results(tasks: list[AwsQuantumTask], max_workers: int) -> list[TaskResult]: with ThreadPoolExecutor(max_workers=max_workers) as executor: result_futures = [executor.submit(task.result) for task in tasks] return [future.result() for future in result_futures] diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 5962cad55..c8b19e146 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections import Counter -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Sequence from numbers import Number from typing import Any, TypeVar @@ -42,8 +42,8 @@ check_noise_target_unitary, wrap_with_list, ) -from braket.circuits.observable import Observable -from braket.circuits.observables import TensorProduct +from braket.circuits.observable import Observable, euler_angle_parameter_names +from braket.circuits.observables import Sum, TensorProduct from braket.circuits.parameterizable import Parameterizable from braket.circuits.result_type import ( ObservableParameterResultType, @@ -241,6 +241,30 @@ def parameters(self) -> set[FreeParameter]: """ return self._parameters + def with_euler_angles(self, observables: Sequence[Observable] | Sum) -> Circuit: + """Returns a copy of the circuit with parametrized Euler angles on the observables' qubits + + Args: + observables (Sequence[Observable] | Sum): The observables to measure, + or a Sum Hamiltonian + + Returns: + Circuit: A new circuit with parametrized ZXZ Euler angle rotations appended to the end + on each qubit targeted by any of the observables. + """ + new_circuit = Circuit(self) + targets = ( + {target for obs in observables.summands for target in obs.targets} + if isinstance(observables, Sum) + else {target for obs in observables for target in obs.targets} + ) + for target in targets: + params = euler_angle_parameter_names(target) + new_circuit.rz(target, FreeParameter(params[0])) + new_circuit.rx(target, FreeParameter(params[1])) + new_circuit.rz(target, FreeParameter(params[2])) + return new_circuit + def add_result_type( self, result_type: ResultType, @@ -1247,10 +1271,23 @@ def to_ir( "serialization_properties must be of type OpenQASMSerializationProperties " "for IRType.OPENQASM." ) - return self._to_openqasm( - serialization_properties or OpenQASMSerializationProperties(), - gate_definitions.copy(), - ) + if not serialization_properties: + qubit_reference_type = ( + QubitReferenceType.PHYSICAL + if ( + gate_definitions + or any( + instruction.operator.requires_physical_qubits + for instruction in self.instructions + ) + ) + else QubitReferenceType.VIRTUAL + ) + serialization_properties = OpenQASMSerializationProperties( + qubit_reference_type=qubit_reference_type + ) + + return self._to_openqasm(serialization_properties, gate_definitions.copy()) raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @staticmethod diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index c039b561f..0cd325776 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -29,6 +29,10 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return EndVerbatimBox() + @property + def requires_physical_qubits(self) -> bool: + return True + def _to_jaqcd(self, *args, **kwargs) -> Any: return ir.StartVerbatimBox.construct() @@ -47,6 +51,10 @@ def __init__(self): def counterpart(self) -> CompilerDirective: return StartVerbatimBox() + @property + def requires_physical_qubits(self) -> bool: + return True + def _to_jaqcd(self, *args, **kwargs) -> Any: return ir.EndVerbatimBox.construct() diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index dafc40c73..056134e2b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -3764,6 +3764,10 @@ def parameters(self) -> list[FreeParameter]: r"""Returns the list of `FreeParameter` s associated with the gate.""" return list(self._pulse_sequence.parameters) + @property + def requires_physical_qubits(self) -> bool: + return True + def bind_values(self, **kwargs) -> PulseGate: """Takes in parameters and returns an object with specified parameters replaced with their values. diff --git a/src/braket/circuits/observable.py b/src/braket/circuits/observable.py index 7e86ab33e..6f3c48115 100644 --- a/src/braket/circuits/observable.py +++ b/src/braket/circuits/observable.py @@ -28,6 +28,11 @@ ) from braket.registers import QubitInput, QubitSet, QubitSetInput +EULER_OBSERVABLE_PREFIX = "_OBSERVABLE_" +EULER_Z1_PREFIX = f"{EULER_OBSERVABLE_PREFIX}THETA_" +EULER_X_PREFIX = f"{EULER_OBSERVABLE_PREFIX}PHI_" +EULER_Z2_PREFIX = f"{EULER_OBSERVABLE_PREFIX}OMEGA_" + class Observable(QuantumOperator): """Class `Observable` to represent a quantum observable. @@ -123,29 +128,23 @@ def targets(self) -> QubitSet | None: @property def coefficient(self) -> int: - """The coefficient of the observable. - - Returns: - int: coefficient value of the observable. - """ + """int: coefficient value of the observable.""" return self._coef @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - """Returns the basis rotation gates for this observable. + """tuple[Gate, ...]: The basis rotation gates for this observable.""" + raise NotImplementedError - Returns: - tuple[Gate, ...]: The basis rotation gates for this observable. - """ + @property + def euler_angles(self) -> dict[str, float]: + """dict[str, float]: A mapping of standardized free parameter name to ZXZ Euler angle value + that diagonalizes this observable.""" raise NotImplementedError @property def eigenvalues(self) -> np.ndarray: - """Returns the eigenvalues of this observable. - - Returns: - np.ndarray: The eigenvalues of this observable. - """ + """np.ndarray: The eigenvalues of this observable.""" raise NotImplementedError def eigenvalue(self, index: int) -> float: @@ -242,3 +241,11 @@ def ascii_symbols(self) -> tuple[str, ...]: f"{self.coefficient if self.coefficient != 1 else ''}{ascii_symbol}" for ascii_symbol in self._ascii_symbols ) + + +def euler_angle_parameter_names(target: QubitInput) -> tuple[str, str, str]: + return ( + f"{EULER_Z1_PREFIX}{int(target)}", + f"{EULER_X_PREFIX}{int(target)}", + f"{EULER_Z2_PREFIX}{int(target)}", + ) diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 5be86554a..f5a2ce393 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -23,7 +23,7 @@ import numpy as np from braket.circuits.gate import Gate -from braket.circuits.observable import Observable, StandardObservable +from braket.circuits.observable import Observable, StandardObservable, euler_angle_parameter_names from braket.circuits.quantum_operator_helpers import ( get_pauli_eigenvalues, is_hermitian, @@ -124,6 +124,11 @@ def to_matrix(self) -> np.ndarray: def basis_rotation_gates(self) -> tuple[Gate, ...]: return () + @property + def euler_angles(self) -> dict[str, float]: + params = euler_angle_parameter_names(self._targets[0]) + return {params[0]: 0, params[1]: 0, params[2]: 0} + @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. @@ -183,6 +188,11 @@ def to_matrix(self) -> np.ndarray: def basis_rotation_gates(self) -> tuple[Gate, ...]: return (Gate.H(),) + @property + def euler_angles(self) -> dict[str, float]: + params = euler_angle_parameter_names(self._targets[0]) + return {params[0]: np.pi / 2, params[1]: np.pi / 2, params[2]: np.pi / 2} + Observable.register_observable(X) @@ -229,6 +239,11 @@ def to_matrix(self) -> np.ndarray: def basis_rotation_gates(self) -> tuple[Gate, ...]: return Gate.Z(), Gate.S(), Gate.H() + @property + def euler_angles(self) -> dict[str, float]: + params = euler_angle_parameter_names(self._targets[0]) + return {params[0]: 0, params[1]: np.pi / 2, params[2]: np.pi / 2} + Observable.register_observable(Y) @@ -275,6 +290,11 @@ def to_matrix(self) -> np.ndarray: def basis_rotation_gates(self) -> tuple[Gate, ...]: return () + @property + def euler_angles(self) -> dict[str, float]: + params = euler_angle_parameter_names(self._targets[0]) + return {params[0]: 0, params[1]: 0, params[2]: 0} + Observable.register_observable(Z) @@ -410,6 +430,13 @@ def basis_rotation_gates(self) -> tuple[Gate, ...]: gates.extend(obs.basis_rotation_gates) return tuple(gates) + @property + def euler_angles(self) -> dict[str, float]: + angles = {} + for obs in self.factors: + angles.update(obs.euler_angles) + return angles + @property def eigenvalues(self) -> np.ndarray: """Returns the eigenvalues of this observable. @@ -449,8 +476,8 @@ def eigenvalue(self, index: int) -> float: for i in range(len(self._factors)): quotient, remainder = divmod(quotient, self._factor_dimensions[i]) product *= self._factors[-i - 1].eigenvalue(remainder) - self._eigenvalue_indices[index] = product - return self.coefficient * self._eigenvalue_indices[index] + self._eigenvalue_indices[index] = self.coefficient * product + return self._eigenvalue_indices[index] def __repr__(self): return "TensorProduct(" + ", ".join([repr(o) for o in self.factors]) + ")" @@ -458,6 +485,9 @@ def __repr__(self): def __eq__(self, other: TensorProduct): return self.matrix_equivalence(other) + def __len__(self): + return len(self._factors) + @staticmethod def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray: if False in [isinstance(observable, StandardObservable) for observable in observables]: @@ -581,6 +611,9 @@ def __repr__(self): def __eq__(self, other: Sum): return repr(self) == repr(other) + def __len__(self): + return len(self._summands) + @staticmethod def _compute_eigenvalues(observables: tuple[Observable], num_qubits: int) -> np.ndarray: raise NotImplementedError("Eigenvalue calculation not supported for Sum") diff --git a/src/braket/circuits/operator.py b/src/braket/circuits/operator.py index ccd63ac37..54ef6cdd7 100644 --- a/src/braket/circuits/operator.py +++ b/src/braket/circuits/operator.py @@ -33,5 +33,10 @@ def to_ir(self, *args, **kwargs) -> Any: If the operator is passed in a request, this method is called before it is passed. Returns: - Any: The the canonical intermediate representation of the operator. + Any: The canonical intermediate representation of the operator. """ + + @property + def requires_physical_qubits(self) -> bool: + """bool: Whether a circuit using this operator requires qubits to be physical.""" + return False diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 57b7aa7f2..3bddd0ca7 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -11,19 +11,18 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +from __future__ import annotations + import warnings from abc import ABC, abstractmethod -from typing import Any, Optional, Union +from typing import Any, Optional from braket.device_schema import DeviceActionType -from braket.ir.openqasm import Program -from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation -from braket.annealing.problem import Problem from braket.circuits import Circuit, Noise from braket.circuits.noise_model import NoiseModel from braket.circuits.translations import SUPPORTED_NOISE_PRAGMA_TO_NOISE -from braket.tasks.quantum_task import QuantumTask +from braket.tasks.quantum_task import QuantumTask, TaskSpecification from braket.tasks.quantum_task_batch import QuantumTaskBatch @@ -43,7 +42,7 @@ def __init__(self, name: str, status: str): @abstractmethod def run( self, - task_specification: Union[Circuit, Problem], + task_specification: TaskSpecification, shots: Optional[int], inputs: Optional[dict[str, float]], *args, @@ -53,7 +52,7 @@ def run( or an annealing problem. Args: - task_specification (Union[Circuit, Problem]): Specification of a quantum task + task_specification (TaskSpecification): Specification of a quantum task to run on device. shots (Optional[int]): The number of times to run the quantum task on the device. Default is `None`. @@ -70,26 +69,23 @@ def run( @abstractmethod def run_batch( self, - task_specifications: Union[ - Union[Circuit, Problem], - list[Union[Circuit, Problem]], - ], + task_specifications: TaskSpecification | list[TaskSpecification], shots: Optional[int], max_parallel: Optional[int], - inputs: Optional[Union[dict[str, float], list[dict[str, float]]]], + inputs: Optional[dict[str, float] | list[dict[str, float]]], *args: Any, **kwargs: Any, ) -> QuantumTaskBatch: """Executes a batch of quantum tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem], list[Union[Circuit, Problem]]]): + task_specifications (TaskSpecification | list[TaskSpecification]): Single instance or list of circuits or problems to run on device. shots (Optional[int]): The number of times to run the circuit or annealing problem. max_parallel (Optional[int]): The maximum number of quantum tasks to run in parallel. Batch creation will fail if this value is greater than the maximum allowed concurrent quantum tasks on the device. - inputs (Optional[Union[dict[str, float], list[dict[str, float]]]]): Inputs to be + inputs (Optional[dict[str, float] | list[dict[str, float]]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. *args (Any): Arbitrary arguments. @@ -131,8 +127,8 @@ def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: ) def _apply_noise_model_to_circuit( - self, task_specification: Union[Circuit, Problem, Program, AnalogHamiltonianSimulation] - ) -> Union[Circuit, Problem, Program, AnalogHamiltonianSimulation]: + self, task_specification: TaskSpecification + ) -> TaskSpecification: if isinstance(task_specification, Circuit): for instruction in task_specification.instructions: if isinstance(instruction.operator, Noise): diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 8dc2b737c..7762c9da8 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -22,12 +22,14 @@ from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.ir.ahs import Program as AHSProgram from braket.ir.openqasm import Program as OpenQASMProgram +from braket.ir.openqasm.program_set_v1 import ProgramSet as OpenQASMProgramSet from braket.simulator import BraketSimulator from braket.task_result import ( AnalogHamiltonianSimulationTaskResult, AnnealingTaskResult, GateModelTaskResult, ) +from braket.task_result.program_set_task_result_v1 import ProgramSetTaskResult from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem @@ -36,12 +38,15 @@ from braket.circuits.noise_model import NoiseModel from braket.circuits.serialization import IRType, SerializableProgram from braket.devices.device import Device +from braket.program_sets import ProgramSet from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( AnalogHamiltonianSimulationQuantumTaskResult, ) from braket.tasks.local_quantum_task import LocalQuantumTask from braket.tasks.local_quantum_task_batch import LocalQuantumTaskBatch +from braket.tasks.program_set_quantum_task_result import ProgramSetQuantumTaskResult +from braket.tasks.quantum_task import TaskSpecification if sys.version_info.minor == 9: from backports.entry_points_selectable import entry_points @@ -66,7 +71,7 @@ def __init__( """Initializes a `LocalSimulator`. Args: - backend (Union[str, BraketSimulator]): The name of the simulator backend or + backend (str | BraketSimulator): The name of the simulator backend or the actual simulator instance to use for simulation. Defaults to the `default` simulator backend name. noise_model (Optional[NoiseModel]): The Braket noise model to apply to the circuit @@ -85,12 +90,8 @@ def __init__( def run( self, - task_specification: Circuit - | Problem - | OpenQASMProgram - | AnalogHamiltonianSimulation - | SerializableProgram, - shots: int = 0, + task_specification: TaskSpecification, + shots: int | None = None, inputs: Optional[dict[str, float]] = None, *args: Any, **kwargs: Any, @@ -98,8 +99,7 @@ def run( """Runs the given task with the wrapped local simulator. Args: - task_specification (Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]): - The quantum task specification. + task_specification (TaskSpecification): The quantum task specification. shots (int): The number of times to run the circuit or annealing problem. Default is 0, which means that the simulator will compute the exact results based on the quantum task specification. @@ -122,39 +122,35 @@ def run( >>> circuit = Circuit().h(0).cnot(0, 1) >>> device = LocalSimulator("default") >>> device.run(circuit, shots=1000) - """ # noqa: E501 + """ if self._noise_model: task_specification = self._apply_noise_model_to_circuit(task_specification) payload = self._construct_payload(task_specification, inputs, shots) + shots = shots if shots is not None else self._default_shots(task_specification) result = self._delegate.run(payload, *args, shots=shots, **kwargs) - return LocalQuantumTask(self._to_result_object(result)) + return LocalQuantumTask( + self._to_result_object(result, task_specification=task_specification) + ) def run_batch( self, - task_specifications: Circuit - | Problem - | OpenQASMProgram - | AnalogHamiltonianSimulation - | SerializableProgram - | list[ - Circuit | Problem | OpenQASMProgram | AnalogHamiltonianSimulation | SerializableProgram - ], - shots: int | None = 0, - max_parallel: int | None = None, - inputs: dict[str, float] | list[dict[str, float]] | None = None, + task_specifications: TaskSpecification | list[TaskSpecification], + shots: Optional[int] = 0, + max_parallel: Optional[int] = None, + inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, *args, **kwargs, ) -> LocalQuantumTaskBatch: """Executes a batch of quantum tasks in parallel Args: - task_specifications (Union[Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram], list[Union[Circuit, Problem, OpenQASMProgram, AnalogHamiltonianSimulation, SerializableProgram]]]): # noqa + task_specifications (TaskSpecification | list[TaskSpecification]): Single instance or list of quantum task specification. shots (Optional[int]): The number of times to run the quantum task. Default: 0. max_parallel (Optional[int]): The maximum number of quantum tasks to run in parallel. Default is the number of logical CPUs. - inputs (Optional[Union[dict[str, float], list[dict[str, float]]]]): Inputs to be passed + inputs (Optional[dict[str, float] | list[dict[str, float]]]): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. @@ -254,12 +250,12 @@ def _construct_payload( self, task_specification: Any, inputs: dict[str, float] | None, - shots: int | None, + shots: int, ) -> Any: raise NotImplementedError(f"Unsupported task type {type(task_specification)}") @_construct_payload.register - def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: Optional[int]): + def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: int): simulator = self._delegate if DeviceActionType.OPENQASM in simulator.properties.action: validate_circuit_and_shots(circuit, shots) @@ -285,6 +281,26 @@ def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots ) return program + @_construct_payload.register + def _(self, program_set: ProgramSet, inputs: Optional[dict[str, float]], _shots: int): + if inputs: + raise ValueError( + "Inputs for program sets must be provided in the program set object. " + "Include your input parameters when creating program sets. " + "`device.run()` does not support the `inputs` argument for program set." + ) + return program_set.to_ir() + + @_construct_payload.register + def _(self, program_set: OpenQASMProgramSet, inputs: Optional[dict[str, float]], _shots: int): + if inputs: + raise ValueError( + "Inputs for program sets must be provided in the program set object. " + "Include your input parameters when creating program sets. " + "`device.run()` does not support the `inputs` argument for program set." + ) + return program_set + @_construct_payload.register def _(self, program: SerializableProgram, inputs: Optional[dict[str, float]], _shots: int): inputs_copy = inputs.copy() if inputs is not None else {} @@ -318,23 +334,37 @@ def _(self, problem: Problem, _inputs: dict[str, float], _shots: int): return problem.to_ir() @singledispatchmethod - def _to_result_object(self, result: Any) -> Any: + def _to_result_object(self, result: Any, **kwargs) -> Any: raise NotImplementedError(f"Unsupported task result type {type(result)}") @_to_result_object.register - def _(self, result: GateModelQuantumTaskResult) -> GateModelQuantumTaskResult: + def _(self, result: GateModelQuantumTaskResult, **kwargs) -> GateModelQuantumTaskResult: return result @_to_result_object.register - def _(self, result: GateModelTaskResult) -> GateModelQuantumTaskResult: + def _(self, result: GateModelTaskResult, **kwargs) -> GateModelQuantumTaskResult: return GateModelQuantumTaskResult.from_object(result) @_to_result_object.register def _( - self, result: AnalogHamiltonianSimulationTaskResult + self, result: ProgramSetTaskResult, task_specification: Optional[TaskSpecification] = None + ): + return ProgramSetQuantumTaskResult.from_object(result, task_specification) + + @_to_result_object.register + def _( + self, result: AnalogHamiltonianSimulationTaskResult, **kwargs ) -> AnalogHamiltonianSimulationQuantumTaskResult: return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) @_to_result_object.register - def _(self, result: AnnealingTaskResult) -> AnnealingQuantumTaskResult: + def _(self, result: AnnealingTaskResult, **kwargs) -> AnnealingQuantumTaskResult: return AnnealingQuantumTaskResult.from_object(result) + + @staticmethod + def _default_shots(task_specification: TaskSpecification) -> int: + if isinstance(task_specification, (ProgramSet, OpenQASMProgramSet)): + if not task_specification.shots_per_executable: + raise ValueError("Shots must be specified in program set or during task creation") + return task_specification.total_shots + return 0 diff --git a/src/braket/program_sets/__init__.py b/src/braket/program_sets/__init__.py new file mode 100644 index 000000000..387d65660 --- /dev/null +++ b/src/braket/program_sets/__init__.py @@ -0,0 +1,16 @@ +# 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. + +from braket.program_sets.circuit_binding import CircuitBinding # noqa: F401 +from braket.program_sets.parameter_sets import ParameterSets, ParameterSetsLike # noqa: F401 +from braket.program_sets.program_set import ProgramSet # noqa: F401 diff --git a/src/braket/program_sets/circuit_binding.py b/src/braket/program_sets/circuit_binding.py new file mode 100644 index 000000000..ef640e436 --- /dev/null +++ b/src/braket/program_sets/circuit_binding.py @@ -0,0 +1,157 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from typing import Optional + +from braket.ir.openqasm import Program + +from braket.circuits import Circuit, Gate, Observable +from braket.circuits.observable import euler_angle_parameter_names +from braket.circuits.observables import Sum +from braket.circuits.serialization import IRType +from braket.program_sets.parameter_sets import ParameterSets, ParameterSetsLike +from braket.pulse import PulseSequence +from braket.registers import QubitSet + + +class CircuitBinding: + def __init__( + self, + circuit: Circuit, + input_sets: Optional[ParameterSetsLike] = None, + observables: Optional[Sequence[Observable] | Sum] = None, + ): + """ + A single parametrized circuit and multiple parameter sets and observables. + + In other words, running a circuit binding means running the circuit with each set of input + parameters specified. Furthermore, observables are encoded as input parameters by way of + Euler angle representation. + + If both input parameters and observables are provided, then each combination is executed, + resulting in a total number of runs equal to the product of the two. For example, if there + are 3 input sets and 4 parameters (or a Hamiltonian with 4 terms), the circuit will be run + 12 times. + + Note: Circuits cannot have result types attached. + + Args: + circuit (Circuit): The parametrized circuit + input_sets (Optional[ParameterSetsLike]): The inputs to the circuit, if specified. + observables (Optional[Sequence[Observable] | Sum]): The observables or Hamiltonian + to measure, if specified. + + Examples: + >>> circuit = Circuit().rx(0, FreeParameter("theta")).cnot(0, 1) + >>> observable = 2.1 * X(0) @ Z(1) - 4.5 * Z(0) @ Y(1) # Sum Hamiltonian + >>> # observable = [X(0) @ Z(1), Z(0) @ Y(1)] # Or a list of single-term observables + >>> binding = CircuitBinding(circuit, {"theta": [1.23, 1.73, 0.73]}, observable) + """ + if not input_sets and not observables: + raise ValueError("At least one of input_sets and observables must be specified") + if observables: + terms = observables.summands if isinstance(observables, Sum) else observables + if any(isinstance(obs, Sum) for obs in terms): + raise TypeError("Cannot have Sum Hamiltonian in list of observables") + if circuit.result_types: + raise ValueError("Circuit cannot have result types") + self._circuit = circuit + self._input_sets = ParameterSets(input_sets) if input_sets else None + self._observables = observables or None + + @property + def circuit(self) -> Circuit: + """ + Circuit: The parametrized circuit + """ + return self._circuit + + @property + def input_sets(self) -> Optional[ParameterSets]: + """ + Optional[ParameterSets]: The inputs to the circuit, if specified. + """ + return self._input_sets + + @property + def observables(self) -> Optional[Sequence[Observable] | Sum]: + """ + Optional[Sequence[Observable] | Sum]: The observables or qubit Hamiltonian to measure, + if specified. + """ + return self._observables + + def to_ir( + self, + *, + gate_definitions: Mapping[tuple[Gate, QubitSet], PulseSequence] | None = None, + ) -> Program: + """Serializes the circuit binding into a form that can run on a Braket device. + + Observables are treated as input parameters via conversion to Euler angles. + + Args: + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): The + calibration data for the device. default: None. + + Returns: + Program: An OpenQASM program containing the serialized circuit and input parameters. + """ + if observables := self._observables: + terms = observables.summands if isinstance(observables, Sum) else observables + euler_angles = {} + for target in {target for obs in terms for target in obs.targets}: + for param in euler_angle_parameter_names(target): + euler_angles[param] = [obs.euler_angles.get(param, 0) for obs in terms] + return Program( + source=self._circuit.with_euler_angles(observables) + .to_ir(IRType.OPENQASM, gate_definitions=gate_definitions) + .source, + inputs=( + self._input_sets * euler_angles + if self._input_sets + else ParameterSets(euler_angles) + ).as_dict(), + ) + return Program( + source=self._circuit.to_ir(IRType.OPENQASM, gate_definitions=gate_definitions).source, + inputs=self._input_sets.as_dict() if self._input_sets else None, + ) + + def __len__(self): + input_sets = self._input_sets + observables = self._observables + if input_sets and observables: + return len(input_sets) * len(observables) + if input_sets: + return len(input_sets) + return len(observables) + + def __eq__(self, other: CircuitBinding): + if not isinstance(other, CircuitBinding): + return False + return ( + self._circuit == other._circuit + and self._input_sets == other._input_sets + and self._observables == other._observables + ) + + def __repr__(self): + return ( + f"CircuitBinding(circuit={self._circuit!r}, " + f"input_sets={self._input_sets}, " + f"observables={self._observables})" + ) diff --git a/src/braket/program_sets/parameter_sets.py b/src/braket/program_sets/parameter_sets.py new file mode 100644 index 000000000..bd328dfb4 --- /dev/null +++ b/src/braket/program_sets/parameter_sets.py @@ -0,0 +1,167 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from typing import Union + +import numpy as np + +ParameterSetsLike = Union[ + "ParameterSets", Sequence[Mapping[str, float]], Mapping[str, Sequence[float]] +] + + +class ParameterSets: + def __init__( + self, + parameter_sets: ParameterSetsLike | None = None, + *, + keys: Sequence[str] | None = None, + values: Sequence[Sequence[float]] | np.ndarray | None = None, + **kwargs, + ): + """ + Stores a sequence of parameter sets for a parametrized circuit. + + Exactly one of inputs, keys and values, or kwargs must be specified. + + Args: + parameter_sets (ParameterSetsLike | None): Multiple + inputs to a parametrized circuit; must either be a list of dicts, where every dict + has the same parameter names for keys, or a dict mapping parameter names to lists of + values, where each list must be the same length. Default: None. + keys (Sequence[str] | None): A list of parameter names as strings. If specified, values + must also be specified. Default: None. + values (Sequence[Sequence[float]] | ndarray | None): A list of parameter value lists, + in the same order as keys; each list must be the same length. Default: None. + **kwargs: Keys are parameter names, while values are lists of values; each list must be + the same length. + """ + ParameterSets._validate_combinations(parameter_sets, keys=keys, values=values, **kwargs) + if parameter_sets: + if isinstance(parameter_sets, ParameterSets): + self._inputs = parameter_sets._inputs + elif isinstance(parameter_sets, Mapping): + self._inputs = ParameterSets._mapping_to_dict(parameter_sets) + elif isinstance(parameter_sets, Sequence): + self._inputs = ParameterSets._sequence_to_dict(parameter_sets) + else: + raise TypeError(f"Unsupported type {type(parameter_sets)} for ParameterSets") + elif keys: + self._inputs = ParameterSets._mapping_to_dict(dict(_strict_zip(keys, values))) + elif kwargs: + self._inputs = ParameterSets._mapping_to_dict(kwargs) + else: + self._inputs = None + + def as_dict(self) -> dict[str, list[float]]: + """The keys and corresponding value lists of this ParameterSets object. + + Returns: + dict[str, list[float]]: A dict mapping parameter names to lists of values. + """ + return self._inputs + + def as_list(self) -> list[dict[str, float]]: + """A list of dicts mapping parameters to the ith value + + Returns: + list[dict[str, float]]: A list of dicts mapping parameter names to values. + """ + items = self._inputs.items() + return [{k: v[i] for k, v in items} for i in range(len(self))] + + def __len__(self): + return len(next(iter(self._inputs.values()))) if self._inputs else 0 + + def __add__(self, other: ParameterSetsLike): + other_cast = other if isinstance(other, ParameterSets) else ParameterSets(other) + if set(self._inputs.keys()) != set(other_cast._inputs.keys()): + raise ValueError("Mismatched keys between parameter sets") + other_dict = other_cast.as_dict() or {} + return ParameterSets({k: v + other_dict[k] for k, v in self.as_dict().items()}) + + def __mul__(self, other: ParameterSetsLike): + other_cast = other if isinstance(other, ParameterSets) else ParameterSets(other) + product = { + k: list(sum(_strict_zip(*([v] * len(other_cast))), ())) + for k, v in self.as_dict().items() + } + other_multiplied = {k: v * len(self) for k, v in (other_cast.as_dict() or {}).items()} + product.update(other_multiplied) + return ParameterSets(product) + + def __repr__(self): + return repr(self.as_dict()) + + def __eq__(self, other: ParameterSetsLike): + if isinstance(other, ParameterSets): + return self._inputs == other._inputs + try: + return self._inputs == ParameterSets(other)._inputs + except Exception: + return False + + @staticmethod + def _sequence_to_dict(inputs_list: Sequence[Mapping[str, float]]) -> dict[str, list[float]]: + converted = {k: [v] for k, v in inputs_list[0].items()} + for inputs in inputs_list[1:]: + remaining = set(converted.keys()) + for k, v in inputs.items(): + if k not in converted: + raise ValueError("Mismatched keys in inputs_list") + converted[k].append(v) + remaining.remove(k) + if remaining: + raise ValueError("Mismatched keys in inputs_list") + return converted + + @staticmethod + def _mapping_to_dict(inputs_dict: Mapping[str, Sequence[float]]) -> dict[str, list[float]]: + items = list(inputs_dict.items()) + k0, v0 = items[0] + inputs = {k0: list(v0)} + length = len(v0) + for k, v in items[1:]: + if len(v) != length: + raise ValueError("List of values must be identical for all keys") + inputs[k] = list(v) + return inputs + + @staticmethod + def _validate_combinations( + parameter_sets: ParameterSetsLike | None, + *, + keys: Sequence[str] | None, + values: Sequence[Sequence[float]] | np.ndarray | None, + **kwargs, + ) -> None: + if parameter_sets and (keys is not None or values is not None): + raise ValueError("Can only populate one of inputs or key-value pairs") + if parameter_sets and kwargs: + raise ValueError("Can only populate one of inputs or kwargs") + if kwargs and (keys is not None or values is not None): + raise ValueError("Can only populate one of kwargs or key-value pairs") + if (keys is not None and values is None) or (values is not None and keys is None): + raise ValueError("Both keys and values must be specified") + + +def _strict_zip(*args) -> zip: + # TODO: Remove and replace all usage with zip(..., strict=True) once we drop Python 3.9 support + it = iter(args) + length = len(next(it)) + if not all(len(lst) == length for lst in it): + raise ValueError("Lists must be of equal length") + return zip(*args) diff --git a/src/braket/program_sets/program_set.py b/src/braket/program_sets/program_set.py new file mode 100644 index 000000000..4b79679ef --- /dev/null +++ b/src/braket/program_sets/program_set.py @@ -0,0 +1,289 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Mapping, Sequence + +from braket.ir.openqasm import ProgramSet as OpenQASMProgramSet + +from braket.circuits import Circuit, Gate, Observable +from braket.circuits.observables import Sum +from braket.circuits.serialization import IRType +from braket.program_sets.circuit_binding import CircuitBinding +from braket.program_sets.parameter_sets import _strict_zip +from braket.pulse import PulseSequence +from braket.registers import QubitSet + + +class ProgramSet: + def __init__( + self, + programs: list[CircuitBinding | Circuit] | CircuitBinding, + shots_per_executable: int | None = None, + ): + """ + A set of programs to be run together on a device. + + Args: + programs (list[CircuitBinding | Circuit] | CircuitBinding): A list of circuit bindings + or circuits to execute. It is also possible to provide a single circuit binding. + Note: circuits cannot have result types. + shots_per_executable (int | None): The number of shots to run each executable; + this will be used to enforce the total shots on task creation. If not provided, + the only validation at task creation will be divisibility by number of executables. + """ + self._programs = [programs] if isinstance(programs, CircuitBinding) else programs + if any(isinstance(circuit, Circuit) and circuit.result_types for circuit in self._programs): + raise ValueError("Circuit cannot have result types") + self._shots_per_executable = shots_per_executable + + def to_ir( + self, + *, + gate_definitions: Mapping[tuple[Gate, QubitSet], PulseSequence] | None = None, + ) -> OpenQASMProgramSet: + """Serializes the program set into a form that can run on a Braket device. + + Args: + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): The + calibration data for the device. default: None. + + Returns: + braket.ir.openqasm.ProgramSet: The serialized program set. + """ + return OpenQASMProgramSet( + programs=[ + ( + circuit_binding.to_ir(IRType.OPENQASM, gate_definitions=gate_definitions) + if isinstance(circuit_binding, Circuit) + else circuit_binding.to_ir(gate_definitions=gate_definitions) + ) + for circuit_binding in self._programs + ] + ) + + @property + def entries(self) -> list[CircuitBinding | Circuit]: + """list[CircuitBinding | Circuit]: The circuit bindings or circuits in this program set""" + return self._programs + + @property + def total_executables(self) -> int: + """int: The total number of executables in this program set""" + return sum(len(prog) if isinstance(prog, CircuitBinding) else 1 for prog in self._programs) + + @property + def shots_per_executable(self) -> int | None: + """int: The number of shots to run each executable in this program set""" + return self._shots_per_executable + + @property + def total_shots(self) -> int: + """ + int: The total number of shots across all executables in this program set, + if shots_per_executable was provided. + """ + if not self._shots_per_executable: + raise ValueError("No per-executable shots defined") + return self._shots_per_executable * self.total_executables + + @staticmethod + def zip( + circuits: Sequence[Circuit] | CircuitBinding, + *, + input_sets: Sequence[Mapping[str, float]] | None = None, + observables: Sequence[Observable | None] | None = None, + shots_per_executable: int | None = None, + ) -> ProgramSet: + """ + Constructs a batch of circuits from a list of circuits and optionally an input set and/or + observable for each; alternatively, a single CircuitBinding can be provided and paired + with corresponding observables. + + Args: + circuits (Sequence[Circuit] | CircuitBinding): The parametrized circuit with parameters + or set of fixed circuits to run with multiple observables. + input_sets (Sequence[Mapping[str, float]] | None): The inputs to the circuit; + must match number of circuits if provided. + Must be empty if circuits is a CircuitBinding. + observables (Sequence[Observable | None]| None): A set of observables to measure + with the circuits; must match number of circuits if provided. + shots_per_executable (int | None): The number of shots to run each executable; + this will be used to enforce the total shots on task creation. If not provided, + the only validation at task creation will be divisibility by number of executables. + + Returns: + ProgramSet: a program set consisting of matching sets of + circuits, inputs and observables. + """ + if isinstance(circuits, CircuitBinding): + return _zip_circuit_bindings(circuits, input_sets, observables, shots_per_executable) + return _zip_circuits(circuits, input_sets, observables, shots_per_executable) + + @staticmethod + def product( + circuits: Sequence[Circuit | CircuitBinding], + observables: Sum | Sequence[Observable], + shots_per_executable: int | None = None, + ) -> ProgramSet: + """ + Constructs a program set from the Cartesian product of the given observables with the given + circuits or bindings. + + If an entry of the list is a single circuit, then the resulting program will consist of that + circuit and all the observables; if an entry is a circuit binding, then the result program + will be the Cartesian product of the binding's input values and observables. + + Args: + circuits (Sequence[Circuit] | CircuitBinding): The parametrized circuit with parameters + or set of fixed circuits to run with multiple observables. + observables (Sum | Sequence[Observable]): A set of observables to measure + with the circuits. + shots_per_executable (int | None): The number of shots to run each executable; + this will be used to enforce the total shots on task creation. If not provided, + the only validation at task creation will be divisibility by number of executables. + + Returns: + ProgramSet: a program set consisting of Cartesian products of the given observables + with the given circuits or bindings. + """ + if not observables: + raise ValueError("Observables must be specified") + programs = [] + for circuit in circuits: + if isinstance(circuit, CircuitBinding): + if circuit.observables: + raise ValueError( + "Cannot specify observables in both circuit bindings and product" + ) + programs.append(CircuitBinding(circuit.circuit, circuit.input_sets, observables)) + else: + programs.append(CircuitBinding(circuit, input_sets=None, observables=observables)) + return ProgramSet(programs, shots_per_executable) + + def __len__(self): + return len(self._programs) + + def __getitem__(self, item: int): + return self._programs[item] + + def __add__(self, other: ProgramSet | list): + if isinstance(other, ProgramSet): + if ( + self._shots_per_executable == other._shots_per_executable + or other._shots_per_executable is None + ): + return ProgramSet(self._programs + other._programs, self._shots_per_executable) + if self._shots_per_executable is None: + return ProgramSet(self._programs + other._programs, other._shots_per_executable) + raise ValueError("Mismatched shots per executable") + if isinstance(other, list): + return ProgramSet(self._programs + other, self._shots_per_executable) + raise TypeError(f"Cannot add type {type(other)} to ProgramSet") + + def __eq__(self, other: ProgramSet): + if not isinstance(other, ProgramSet): + return False + return ( + self._programs == other._programs + and self._shots_per_executable == other._shots_per_executable + ) + + def __repr__(self): + return ( + f"ProgramSet(programs={self._programs}, " + f"shots_per_executable={self._shots_per_executable})" + ) + + +def _zip_circuit_bindings( + circuit_binding: CircuitBinding, + input_sets: Sequence[Mapping[str, float]] | None, + observables: Sequence[Observable | None] | None, + shots_per_executable: int | None, +) -> ProgramSet: + circuit = circuit_binding.circuit + if circuit_binding.observables: + if isinstance(circuit_binding.observables, Sum): + raise TypeError("Cannot zip with Sum observable") + if observables: + raise ValueError("Cannot specify observables in both circuit bindings and zip") + if not input_sets: + raise ValueError("Must specify input sets") + if len(circuit_binding.observables) != len(input_sets): + raise ValueError("Number of observables must match number of input sets") + return ProgramSet( + [ + CircuitBinding(circuit, [input_set], [observable]) + for input_set, observable in _strict_zip(input_sets, circuit_binding.observables) + ], + shots_per_executable, + ) + if input_sets: + raise ValueError("Cannot specify input sets in both circuit bindings and zip") + if not observables: + raise ValueError("Must specify observables") + if any(isinstance(obs, Sum) for obs in observables): + raise TypeError("Cannot have Sum Hamiltonian in list of observables") + if len(circuit_binding.input_sets) != len(observables): + raise ValueError("Number of observables must match number of input sets") + inputs_list = circuit_binding.input_sets.as_list() + return ProgramSet( + [ + CircuitBinding(circuit, [input_set], [observable]) + for input_set, observable in _strict_zip(inputs_list, observables) + ], + shots_per_executable, + ) + + +def _zip_circuits( + circuits: Sequence[Circuit], + input_sets: Sequence[Mapping[str, float]] | None, + observables: Sequence[Observable | None] | None, + shots_per_executable: int | None, +) -> ProgramSet: + if input_sets and observables: + if len(circuits) != len(observables): + raise ValueError("Number of circuits must match number of observables") + if len(circuits) != len(input_sets): + raise ValueError("Number of circuits must match number of input sets") + return ProgramSet( + [ + CircuitBinding(circuit, [input_set], [observable]) + for circuit, input_set, observable in _strict_zip(circuits, input_sets, observables) + ], + shots_per_executable, + ) + if input_sets: + if len(circuits) != len(input_sets): + raise ValueError("Number of circuits must match number of input sets") + return ProgramSet( + [ + CircuitBinding(circuit, [input_set], None) + for circuit, input_set in _strict_zip(circuits, input_sets) + ], + shots_per_executable, + ) + if observables: + if len(circuits) != len(observables): + raise ValueError("Number of circuits must match number of observables") + return ProgramSet( + [ + CircuitBinding(circuit, None, [observable]) + for circuit, observable in _strict_zip(circuits, observables) + ], + shots_per_executable, + ) + raise ValueError("Must specify either input sets or observables") diff --git a/src/braket/tasks/__init__.py b/src/braket/tasks/__init__.py index 1df9081c0..69ae708b9 100644 --- a/src/braket/tasks/__init__.py +++ b/src/braket/tasks/__init__.py @@ -21,6 +21,7 @@ from braket.tasks.photonic_model_quantum_task_result import ( PhotonicModelQuantumTaskResult, # noqa: F401 ) +from braket.tasks.program_set_quantum_task_result import ProgramSetQuantumTaskResult # noqa: F401 from braket.tasks.quantum_task import QuantumTask # noqa: F401 from braket.tasks.quantum_task_batch import QuantumTaskBatch # noqa: F401 diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index f1effa71f..573416949 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -30,8 +30,17 @@ TaskMetadata, ) -from braket.circuits import Observable, ResultType, StandardObservable -from braket.circuits.observables import TensorProduct, observable_from_ir +from braket.circuits import Observable, ResultType +from braket.circuits.observables import observable_from_ir +from braket.tasks.measurement_utils import ( + expectation_from_measurements, + measurement_counts_from_measurements, + measurement_probabilities_from_measurement_counts, + measurements_base_10, + measurements_from_measurement_probabilities, + samples_from_measurements, + selected_measurements, +) T = TypeVar("T") @@ -162,10 +171,7 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: Counter: A Counter of measurements. Key is the measurements in a big endian binary string. Value is the number of times that measurement occurred. """ - bitstrings = [ - "".join([str(element) for element in measurements[j]]) for j in range(len(measurements)) - ] - return Counter(bitstrings) + return measurement_counts_from_measurements(measurements) @staticmethod def measurement_probabilities_from_measurement_counts( @@ -182,9 +188,7 @@ def measurement_probabilities_from_measurement_counts( dict[str, float]: A dictionary of probabilistic results. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. """ - shots = sum(measurement_counts.values()) - - return {key: count / shots for key, count in measurement_counts.items()} + return measurement_probabilities_from_measurement_counts(measurement_counts) @staticmethod def measurements_from_measurement_probabilities( @@ -203,12 +207,7 @@ def measurements_from_measurement_probabilities( Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. """ - measurements_list = [] - for bitstring, prob in measurement_probabilities.items(): - measurement = list(bitstring) - individual_measurement_list = [measurement] * round(prob * shots) - measurements_list.extend(individual_measurement_list) - return np.asarray(measurements_list, dtype=int) + return measurements_from_measurement_probabilities(measurement_probabilities, shots) @staticmethod def from_object(result: GateModelTaskResult) -> GateModelQuantumTaskResult: @@ -372,7 +371,7 @@ def _calculate_result_types( casted_result_type = Probability(targets=targets) elif rt_type == "sample": value = GateModelQuantumTaskResult._calculate_for_targets( - GateModelQuantumTaskResult._samples_from_measurements, + samples_from_measurements, measurements, measured_qubits, observable, @@ -390,7 +389,7 @@ def _calculate_result_types( casted_result_type = Variance(targets=targets, observable=ir_observable) elif rt_type == "expectation": value = GateModelQuantumTaskResult._calculate_for_targets( - GateModelQuantumTaskResult._expectation_from_measurements, + expectation_from_measurements, measurements, measured_qubits, observable, @@ -402,16 +401,6 @@ def _calculate_result_types( result_types.append(ResultTypeValue.construct(type=casted_result_type, value=value)) return result_types - @staticmethod - def _selected_measurements( - measurements: np.ndarray, measured_qubits: list[int], targets: list[int] | None - ) -> np.ndarray: - if targets is not None and targets != measured_qubits: - # Only some qubits targeted - columns = [measured_qubits.index(t) for t in targets] - measurements = measurements[:, columns] - return measurements - @staticmethod def _calculate_for_targets( calculate_function: Callable[[np.ndarray, list[int], Observable, list[int]], T], @@ -427,22 +416,14 @@ def _calculate_for_targets( for i in measured_qubits ] - @staticmethod - def _measurements_base_10(measurements: np.ndarray) -> np.ndarray: - # convert samples from a list of 0, 1 integers, to base 10 representation - two_powers = 2 ** np.arange(measurements.shape[-1])[::-1] # 2^(n-1), ..., 2, 1 - return measurements @ two_powers - @staticmethod def _probability_from_measurements( measurements: np.ndarray, measured_qubits: list[int], targets: list[int] | None ) -> np.ndarray: - measurements = GateModelQuantumTaskResult._selected_measurements( - measurements, measured_qubits, targets - ) + measurements = selected_measurements(measurements, measured_qubits, targets) shots, num_measured_qubits = measurements.shape # convert measurements from a list of 0, 1 integers, to base 10 representation - indices = GateModelQuantumTaskResult._measurements_base_10(measurements) + indices = measurements_base_10(measurements) # count the basis state occurrences, and construct the probability vector basis_states, counts = np.unique(indices, return_counts=True) @@ -457,43 +438,9 @@ def _variance_from_measurements( observable: Observable, targets: list[int], ) -> float: - samples = GateModelQuantumTaskResult._samples_from_measurements( - measurements, measured_qubits, observable, targets - ) + samples = samples_from_measurements(measurements, measured_qubits, observable, targets) return np.var(samples) - @staticmethod - def _expectation_from_measurements( - measurements: np.ndarray, - measured_qubits: list[int], - observable: Observable, - targets: list[int], - ) -> float: - samples = GateModelQuantumTaskResult._samples_from_measurements( - measurements, measured_qubits, observable, targets - ) - return np.mean(samples) - - @staticmethod - def _samples_from_measurements( - measurements: np.ndarray, - measured_qubits: list[int], - observable: Observable, - targets: list[int], - ) -> np.ndarray: - measurements = GateModelQuantumTaskResult._selected_measurements( - measurements, measured_qubits, targets - ) - if isinstance(observable, StandardObservable): - # Process samples for observables with eigenvalues {1, -1} - return 1 - 2 * measurements.flatten() - # Replace the basis state in the computational basis with the correct eigenvalue. - # Extract only the columns of the basis samples required based on ``targets``. - indices = GateModelQuantumTaskResult._measurements_base_10(measurements) - if isinstance(observable, TensorProduct): - return np.array([observable.eigenvalue(index).real for index in indices]) - return observable.eigenvalues[indices].real - @staticmethod def _result_type_hash(rt_type: dict) -> str: if hasattr(rt_type, "observable") and isinstance(rt_type.observable, list): diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 285c347c8..3243b4ff6 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -14,12 +14,8 @@ import asyncio -from braket.tasks import ( - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, - QuantumTask, -) +from braket.tasks import QuantumTask +from braket.tasks.quantum_task import TaskResult class LocalQuantumTask(QuantumTask): @@ -28,12 +24,7 @@ class LocalQuantumTask(QuantumTask): Since this class is instantiated with the results, cancel() and run_async() are unsupported. """ - def __init__( - self, - result: GateModelQuantumTaskResult - | AnnealingQuantumTaskResult - | PhotonicModelQuantumTaskResult, - ): + def __init__(self, result: TaskResult): self._id = result.task_metadata.id self._result = result @@ -58,9 +49,7 @@ def state(self) -> str: """ return "COMPLETED" - def result( - self, - ) -> GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult: + def result(self) -> TaskResult: return self._result def async_result(self) -> asyncio.Task: diff --git a/src/braket/tasks/local_quantum_task_batch.py b/src/braket/tasks/local_quantum_task_batch.py index a47a47e6a..6f107a1d4 100644 --- a/src/braket/tasks/local_quantum_task_batch.py +++ b/src/braket/tasks/local_quantum_task_batch.py @@ -11,14 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Union - -from braket.tasks import ( - AnnealingQuantumTaskResult, - GateModelQuantumTaskResult, - PhotonicModelQuantumTaskResult, - QuantumTaskBatch, -) +from braket.tasks import QuantumTaskBatch +from braket.tasks.quantum_task import TaskResult class LocalQuantumTaskBatch(QuantumTaskBatch): @@ -27,23 +21,8 @@ class LocalQuantumTaskBatch(QuantumTaskBatch): Since this class is instantiated with the results, cancel() and run_async() are unsupported. """ - def __init__( - self, - results: list[ - Union[ - GateModelQuantumTaskResult, - AnnealingQuantumTaskResult, - PhotonicModelQuantumTaskResult, - ] - ], - ): + def __init__(self, results: list[TaskResult]): self._results = results - def results( - self, - ) -> list[ - Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ] - ]: + def results(self) -> list[TaskResult]: return self._results diff --git a/src/braket/tasks/measurement_utils.py b/src/braket/tasks/measurement_utils.py new file mode 100644 index 000000000..47e410754 --- /dev/null +++ b/src/braket/tasks/measurement_utils.py @@ -0,0 +1,123 @@ +# 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. + +from __future__ import annotations + +from collections import Counter + +import numpy as np + +from braket.circuits import Observable +from braket.circuits.observable import StandardObservable +from braket.circuits.observables import TensorProduct + + +def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: + """Creates measurement counts from measurements + + Args: + measurements (np.ndarray): 2d array - row is shot and column is qubit. + + Returns: + Counter: A Counter of measurements. Key is the measurements in a big endian binary + string. Value is the number of times that measurement occurred. + """ + bitstrings = [ + "".join([str(element) for element in measurements[j]]) for j in range(len(measurements)) + ] + return Counter(bitstrings) + + +def measurement_probabilities_from_measurement_counts( + measurement_counts: Counter, +) -> dict[str, float]: + """Creates measurement probabilities from measurement counts + + Args: + measurement_counts (Counter): A Counter of measurements. Key is the measurements + in a big endian binary string. Value is the number of times that measurement + occurred. + + Returns: + dict[str, float]: A dictionary of probabilistic results. Key is the measurements + in a big endian binary string. Value is the probability the measurement occurred. + """ + shots = sum(measurement_counts.values()) + return {key: count / shots for key, count in measurement_counts.items()} + + +def measurements_from_measurement_probabilities( + measurement_probabilities: dict[str, float], shots: int +) -> np.ndarray: + """Creates measurements from measurement probabilities. + + Args: + measurement_probabilities (dict[str, float]): A dictionary of probabilistic results. + Key is the measurements in a big endian binary string. + Value is the probability the measurement occurred. + shots (int): number of iterations on device. + + Returns: + np.ndarray: 2d array of measurements matching the given probability distribution + and number of shots. + """ + measurements_list = [] + for bitstring, probability in measurement_probabilities.items(): + measurement = list(bitstring) + individual_measurement_list = [measurement] * round(probability * shots) + measurements_list.extend(individual_measurement_list) + return np.asarray(measurements_list, dtype=int) + + +def expectation_from_measurements( + measurements: np.ndarray, + measured_qubits: list[int], + observable: Observable, + targets: list[int], +) -> float: + samples = samples_from_measurements(measurements, measured_qubits, observable, targets) + return np.mean(samples) + + +def samples_from_measurements( + measurements: np.ndarray, + measured_qubits: list[int], + observable: Observable, + targets: list[int], +) -> np.ndarray: + measurements = selected_measurements(measurements, measured_qubits, targets) + if isinstance(observable, StandardObservable): + # Process samples for observables with eigenvalues {coeff, -coeff} with equal weight + return observable.coefficient * (1 - 2 * measurements.flatten()) + # Replace the basis state in the computational basis with the correct eigenvalue. + # Extract only the columns of the basis samples required based on ``targets``. + indices = measurements_base_10(measurements) + if isinstance(observable, TensorProduct): + return np.array([observable.eigenvalue(index).real for index in indices]) + return observable.eigenvalues[indices].real + + +def selected_measurements( + measurements: np.ndarray, measured_qubits: list[int], targets: list[int] | None +) -> np.ndarray: + if targets is not None and targets != measured_qubits: + # Only some qubits targeted + columns = [measured_qubits.index(t) for t in targets] + measurements = measurements[:, columns] + return measurements + + +def measurements_base_10(measurements: np.ndarray) -> np.ndarray: + # convert samples from a list of 0, 1 integers, to base 10 representation + two_powers = 2 ** np.arange(measurements.shape[-1])[::-1] # 2^(n-1), ..., 2, 1 + return measurements @ two_powers diff --git a/src/braket/tasks/program_set_quantum_task_result.py b/src/braket/tasks/program_set_quantum_task_result.py new file mode 100644 index 000000000..d5f5b8152 --- /dev/null +++ b/src/braket/tasks/program_set_quantum_task_result.py @@ -0,0 +1,499 @@ +# 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. + +from __future__ import annotations + +import warnings +from collections import Counter +from collections.abc import Sequence +from dataclasses import dataclass +from typing import Optional + +import boto3 +import numpy as np +from botocore.client import BaseClient +from braket.ir.openqasm import Program +from braket.schema_common import BraketSchemaBase +from braket.task_result import ( + AdditionalMetadata, + ProgramResult, + ProgramSetExecutableFailure, + ProgramSetExecutableResult, + ProgramSetTaskMetadata, + ProgramSetTaskResult, +) + +from braket.circuits import Observable +from braket.circuits.observable import EULER_OBSERVABLE_PREFIX +from braket.circuits.observables import Sum +from braket.program_sets import CircuitBinding, ParameterSets, ProgramSet +from braket.program_sets.parameter_sets import _strict_zip +from braket.tasks.measurement_utils import ( + expectation_from_measurements, + measurement_counts_from_measurements, + measurement_probabilities_from_measurement_counts, + measurements_from_measurement_probabilities, +) + +_PROGRAM_RESULT_SUFFIX = "/results.json" + + +@dataclass +class MeasuredEntry: + """Result of a single executable in a program. + + Args: + measurements (numpy.ndarray): 2d array - row is shot and column is qubit. + The columns are in the order of `measured_qubits`. + counts (Counter): A `Counter` of measurements. Key is the measurements + in a big endian binary string. Value is the number of times that measurement occurred. + probabilities (dict[str, float]): A dictionary of probabilistic results. + Key is the measurements in a big endian binary string. + Value is the probability the measurement occurred. + measured_qubits (list[int]): The indices of the measured qubits. + measurements_from_device (bool): flag whether `measurements` + were copied from device. If false, `measurements` are calculated from device data. + probabilities_from_device (bool): flag whether + `measurement_probabilities` were copied from device. If false, + `measurement_probabilities` are calculated from device data. + program (str): The program this executable ran. + inputs (dict[str, float] | None): The input parameters to this program, if any. + observable (Observable | None): The observable of this program, if any. + """ + + measurements: np.ndarray + counts: Counter + probabilities: dict[str, float] + measured_qubits: list[int] + measurements_from_device: bool + probabilities_from_device: bool + program: str + inputs: dict[str, float] | None + observable: Observable | None + + @staticmethod + def _from_object( + executable_result: ProgramSetExecutableResult, + *, + shots: int, + program: str, + inputs: dict[str, float] | None = None, + observable: Observable | None = None, + ) -> MeasuredEntry: + if executable_result.measurements: + measurements = np.asarray(executable_result.measurements, dtype=int) + m_counts = measurement_counts_from_measurements(measurements) + m_probs = measurement_probabilities_from_measurement_counts(m_counts) + measurements_copied_from_device = True + m_probabilities_copied_from_device = False + elif executable_result.measurementProbabilities: + m_probs = executable_result.measurementProbabilities + measurements = measurements_from_measurement_probabilities(m_probs, shots) + m_counts = measurement_counts_from_measurements(measurements) + measurements_copied_from_device = False + m_probabilities_copied_from_device = True + else: + raise ValueError( + 'One of "measurements" or "measurementProbabilities" must be populated in', + " the result object", + ) + measured_qubits = executable_result.measuredQubits + return MeasuredEntry( + measurements=measurements, + counts=m_counts, + probabilities=m_probs, + measured_qubits=measured_qubits, + measurements_from_device=measurements_copied_from_device, + probabilities_from_device=m_probabilities_copied_from_device, + program=program, + inputs=inputs, + observable=observable, + ) + + def __post_init__(self): + self._expectation = ( + expectation_from_measurements( + self.measurements, + self.measured_qubits, + self.observable, + self.observable.targets, + ) + if self.observable + else None + ) + + @property + def expectation(self) -> float | None: + """ + float | None: The expectation value of this entry's observable if there is one. + """ + # TODO: Use program set payload to calculate expectation + if self._expectation is None: + warnings.warn( + "No observable was measured", + stacklevel=1, + ) + return self._expectation + + +@dataclass +class CompositeEntry: + """Results of a program in a program set + + Args: + entries(list[MeasuredEntry]): The results of each executable in this program + program (Program): The program that was run + inputs (ParameterSets): The input values this program was run with + observables (Sum | list[Observable] | None): The Sum Hamiltonian or observables + that were measured, if any. + shots_per_executable (int): The number of shots each underlying executable was run with + additional_metadata (AdditionalMetadata): Additional metadata about this program + """ + + entries: list[MeasuredEntry] + program: Program + inputs: ParameterSets + observables: Sum | list[Observable] | None + shots_per_executable: int + additional_metadata: AdditionalMetadata + + @staticmethod + def _from_object( + program_result: ProgramResult, + *, + s3_location: tuple[str, str] = (None, None), + s3_client: BaseClient | None = None, + shots_per_executable: int, + observables: Sum | list[Observable] | None = None, + ) -> CompositeEntry: + s3_bucket, s3_prefix = s3_location + program = CompositeEntry._get_program( + program_result.source, s3_bucket, s3_prefix, s3_client + ) + return CompositeEntry( + entries=CompositeEntry._get_executable_results( + program_result.executableResults, + program, + observables, + shots_per_executable, + s3_bucket, + s3_prefix, + s3_client, + ), + program=program, + inputs=CompositeEntry._get_inputs(program, observables), + observables=observables, + shots_per_executable=shots_per_executable, + additional_metadata=program_result.additionalMetadata, + ) + + def __post_init__(self): + self._expectations = ( + self._compute_expectations() if isinstance(self.observables, Sum) else None + ) + + def __len__(self): + return len(self.entries) + + def __getitem__(self, item: int): + return self.entries[item] + + def expectation(self, i: int | None = None) -> float | None: + """ + float | None: The expectation value of the Hamiltonian whose terms are the observables + of the underlying entries, if observables were specified. + """ + expectations = self._expectations + if not expectations: + raise ValueError("No Sum Hamiltonian was measured") + num_expectations = len(expectations) + if i is None and num_expectations > 1: + raise ValueError( + f"There are {num_expectations} expectation values available; returning first one", + ) + i = i or 0 + if i >= num_expectations: + raise ValueError(f"At most {num_expectations} expectation values available") + return expectations[i] + + def _compute_expectations(self) -> dict[int, float]: + num_expectations = len(self.inputs) or 1 + expectations = {} + for i in range(num_expectations): + num_summands = len(self.observables) + start = i * num_summands + expectations[i] = sum( + entry.expectation for entry in self.entries[start : start + num_summands] + ) + return expectations + + @staticmethod + def _get_program( + program: Program | str, + s3_bucket: str | None, + s3_prefix: str | None, + s3_client: BaseClient | None, + ) -> Program: + if not s3_bucket: + return program + return BraketSchemaBase.parse_raw_schema( + _retrieve_s3_object_body(s3_bucket, f"{s3_prefix}/{program}", s3_client) + ) + + @staticmethod + def _get_inputs(program: Program, observables: Sum | list[Observable] | None) -> ParameterSets: + if not observables: + return ParameterSets(program.inputs) + num_observables = len(observables) + return ParameterSets({ + k: v[::num_observables] + for k, v in program.inputs.items() + if not k.startswith(EULER_OBSERVABLE_PREFIX) + }) + + @staticmethod + def _get_executable_results( + executable_results: Sequence[ + ProgramSetExecutableResult | ProgramSetExecutableFailure | str + ], + program: Program, + observables: Sum | list[Observable] | None, + shots_per_executable: int, + s3_bucket: str | None, + s3_prefix: str | None, + s3_client: BaseClient | None, + ) -> list[MeasuredEntry]: + if not s3_bucket: + return [ + CompositeEntry._dispatch_executable_result( + result, program, observables, shots_per_executable + ) + for result in executable_results + ] + executable_list = [] + for result in executable_results: + result_string = _retrieve_s3_object_body(s3_bucket, f"{s3_prefix}/{result}", s3_client) + parsed: ProgramSetExecutableResult = BraketSchemaBase.parse_raw_schema(result_string) + executable_list.append( + CompositeEntry._dispatch_executable_result( + parsed, program, observables, shots_per_executable + ) + ) + return executable_list + + @staticmethod + def _dispatch_executable_result( + result: ProgramSetExecutableResult, + program: Program, + observables: Sum | list[Observable] | None, + shots_per_executable: int, + ) -> MeasuredEntry | ProgramSetExecutableFailure: + observables = observables.summands if isinstance(observables, Sum) else observables + return ( + MeasuredEntry._from_object( + result, + program=program.source, + shots=shots_per_executable, + inputs={k: v[result.inputsIndex] for k, v in program.inputs.items()}, + observable=( + observables[result.inputsIndex % len(observables)] if observables else None + ), + ) + if isinstance(result, ProgramSetExecutableResult) + else result + ) + + +@dataclass +class ProgramSetQuantumTaskResult: + """The result of a program set task. + + Args: + entries (list[CompositeEntry]): The results of each program in this program set + task_metadata (ProgramSetTaskMetadata) The metadata of the task + num_executables (int): The total number of executables in this program set task + program_set (ProgramSet | None): The program set that was run; if specified, + information from the program set such as observable expectation values + can be automatically computed. + """ + + entries: list[CompositeEntry] + task_metadata: ProgramSetTaskMetadata + num_executables: int + program_set: ProgramSet | None + + @staticmethod + def from_object( + result_schema: ProgramSetTaskResult, program_set: Optional[ProgramSet] = None + ) -> ProgramSetQuantumTaskResult: + """ + Create ProgramSetQuantumTaskResult from ProgramSetTaskResult object. + + Args: + result_schema (ProgramSetTaskResult): The result returned by the device; programs + and metadata may be specified as relative S3 paths, in which case they will be + downloaded to populate the instance. + program_set (ProgramSet): The program set that was run; if specified, information from + the program set such as observable expectation values can be automatically computed. + Default: None. + + Returns: + ProgramSetQuantumTaskResult: A ProgramSetQuantumTaskResult based on the given + schema object; all data stored in S3 is downloaded. + """ + s3_bucket, s3_prefix = result_schema.s3Location or (None, None) + # prevent circular import of AwsSession + s3_client = boto3.client("s3") if s3_bucket else None + metadata: ProgramSetTaskMetadata = ProgramSetQuantumTaskResult._get_metadata( + result_schema.taskMetadata, s3_bucket, s3_prefix, s3_client + ) + program_set = program_set if isinstance(program_set, ProgramSet) else None + num_executables = ProgramSetQuantumTaskResult._compute_num_executables(metadata) + shots_per_executable = metadata.requestedShots // num_executables + return ProgramSetQuantumTaskResult( + entries=ProgramSetQuantumTaskResult._get_entries( + result_schema.programResults, + shots_per_executable, + program_set, + s3_bucket, + s3_prefix, + s3_client, + ), + num_executables=num_executables, + task_metadata=metadata, + program_set=program_set, + ) + + def __len__(self): + return len(self.entries) + + def __getitem__(self, item: int): + return self.entries[item] + + @property + def programs(self) -> list[Program]: + """ + list[Program]: The OpenQASM programs specified in the program set + """ + return [entry.program for entry in self.entries] + + @staticmethod + def _get_metadata( + metadata: ProgramSetTaskMetadata | str, + s3_bucket: str | None, + s3_prefix: str | None, + s3_client: BaseClient | None, + ) -> ProgramSetTaskMetadata: + if not s3_bucket: + return metadata + meta_string = _retrieve_s3_object_body(s3_bucket, f"{s3_prefix}/{metadata}", s3_client) + return BraketSchemaBase.parse_raw_schema(meta_string) + + @staticmethod + def _get_entries( + program_results: Sequence[ProgramResult | str], + shots_per_executable: int, + program_set: ProgramSet | None, + s3_bucket: str | None, + s3_prefix: str | None, + s3_client: BaseClient | None, + ) -> list[CompositeEntry | MeasuredEntry]: + if program_set: + entries = [] + for entry, result in _strict_zip(program_set.entries, program_results): + entries.append( + # The program has observables available to compute + ProgramSetQuantumTaskResult._result_to_entry( + result, + shots_per_executable, + s3_prefix=s3_prefix, + s3_bucket=s3_bucket, + s3_client=s3_client, + observables=entry.observables, + ) + if isinstance(entry, CircuitBinding) + # The program has no observables + else ProgramSetQuantumTaskResult._result_to_entry( + result, + shots_per_executable, + s3_prefix=s3_prefix, + s3_bucket=s3_bucket, + s3_client=s3_client, + ) + ) + return entries + return [ + ProgramSetQuantumTaskResult._result_to_entry( + result, + shots_per_executable, + s3_prefix=s3_prefix, + s3_bucket=s3_bucket, + s3_client=s3_client, + ) + for result in program_results + ] + + @staticmethod + def _result_to_entry( + result: ProgramResult | str, + shots_per_executable: int, + # Note: prefix only refers to the part of the S3 prefix after + # the _whole_ task result's prefix + s3_bucket: str | None, + s3_prefix: str | None, + s3_client: BaseClient | None, + observables: Sum | list[Observable] | None = None, + ) -> CompositeEntry | MeasuredEntry: + if isinstance(result, ProgramResult): + return CompositeEntry._from_object( + result, + shots_per_executable=shots_per_executable, + s3_client=None, + s3_location=(None, None), + observables=observables, + ) + result_key = f"{s3_prefix}/{result}" + return CompositeEntry._from_object( + program_result=BraketSchemaBase.parse_raw_schema( + _retrieve_s3_object_body( + s3_bucket, + result_key, + s3_client, + ) + ), + shots_per_executable=shots_per_executable, + s3_client=s3_client, + s3_location=(s3_bucket, result_key.removesuffix(_PROGRAM_RESULT_SUFFIX)), + observables=observables, + ) + + @staticmethod + def _compute_num_executables(metadata: ProgramSetTaskMetadata) -> int: + counter = 0 + for program in metadata.programMetadata: + counter += len(program.executables) + return counter + + +def _retrieve_s3_object_body(s3_bucket: str, s3_object_key: str, s3_client: BaseClient) -> str: + """Retrieve the S3 object body. + + Args: + s3_bucket (str): The S3 bucket name. + s3_object_key (str): The S3 object key within the `s3_bucket`. + s3_client (BaseClient): The S3 client that will be used to download objects. + + Returns: + str: The body of the S3 object. + """ + return s3_client.get_object(Bucket=s3_bucket, Key=s3_object_key)["Body"].read().decode("utf-8") diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index a0c2561c7..eb9edc314 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -15,9 +15,31 @@ from abc import ABC, abstractmethod from typing import Any, Union -from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult +from braket.ir.openqasm import Program as OpenQASMProgram +from braket.ir.openqasm import ProgramSet as OpenQASMProgramSet + +from braket.ahs import AnalogHamiltonianSimulation +from braket.circuits import Circuit +from braket.circuits.serialization import SerializableProgram +from braket.program_sets import ProgramSet +from braket.pulse import PulseSequence +from braket.tasks import AnalogHamiltonianSimulationQuantumTaskResult, ProgramSetQuantumTaskResult from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult -from braket.tasks.photonic_model_quantum_task_result import PhotonicModelQuantumTaskResult + +TaskSpecification = Union[ + Circuit, + SerializableProgram, + ProgramSet, + OpenQASMProgram, + OpenQASMProgramSet, + AnalogHamiltonianSimulation, + PulseSequence, +] +TaskResult = Union[ + GateModelQuantumTaskResult, + ProgramSetQuantumTaskResult, + AnalogHamiltonianSimulationQuantumTaskResult, +] class QuantumTask(ABC): @@ -47,16 +69,13 @@ def state(self) -> str: @abstractmethod def result( self, - ) -> Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ]: + ) -> TaskResult: """Get the quantum task result. Returns: - Union[GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult]: Get - the quantum task result. Call async_result if you want the result in an - asynchronous way. - """ # noqa: E501 + TaskResult: Get the quantum task result. + Call async_result if you want the result in an asynchronous way. + """ @abstractmethod def async_result(self) -> asyncio.Task: diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index cf3c7e0d9..74a39b46d 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -79,10 +79,124 @@ class MockS3: "action": { "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, "instructions": [{"control": 0, "target": 1, "type": "cnot"}], + } + }, + }) + + MOCK_S3_RESULT_PROGRAM_SET = json.dumps({ + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": [ + { + "braketSchemaHeader": { + "name": "braket.task_result.program_result", + "version": "1", + }, + "executableResults": [ + { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_result", + "version": "1", + }, + "measurements": [ + [0, 0], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [0, 0], + [0, 0], + [1, 1], + [1, 1], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + ], + "measuredQubits": [0, 1], + "inputsIndex": 0, + }, + { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_failure", + "version": "1", + }, + "inputsIndex": 0, + "failureMetadata": { + "failureReason": "QPU was sick, should be good again after getting some sleep", + "retryable": True, + "category": "DEVICE", + }, + }, + ], + "source": { + "braketSchemaHeader": { + "name": "braket.ir.openqasm.program", + "version": "1", + }, + "source": "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\nh q[0];\ncnot q[0], q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];", # noqa + "inputs": {"theta": [0.12, 2.1]}, + }, + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 50, + } + }, + } + ], + "taskMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_metadata", + "version": "1", + }, + "id": "arn:aws:braket:us-west-2:667256736152:quantum-task/bfebc86f-e4ed-4d6f-8131-addd1a49d6dc", # noqa + "deviceId": "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + "requestedShots": 120, + "successfulShots": 100, + "programMetadata": [{"executables": [{}]}], + "deviceParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.simulators.gate_model_simulator_device_parameters", + "version": "1", + }, + "paradigmParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + }, + "qubitCount": 5, + "disableQubitRewiring": False, + }, }, + "createdAt": "2024-10-15T19:06:58.986Z", + "endedAt": "2024-10-15T19:07:00.382Z", + "status": "COMPLETED", + "totalFailedExecutables": 1, }, }) + MOCK_S3_RESULT_ANNEALING = json.dumps( + { + "braketSchemaHeader": { + "name": "braket.task_result.annealing_task_result", + "version": "1", + }, + }, + ) + MOCK_S3_RESULT_ANNEALING = json.dumps({ "braketSchemaHeader": { "name": "braket.task_result.annealing_task_result", diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 274357f29..603a4005b 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -10,6 +10,7 @@ # 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. + import io import json import os @@ -21,6 +22,8 @@ import networkx as nx import pytest from botocore.exceptions import ClientError + +from braket.program_sets import ProgramSet from common_test_utils import ( DM1_ARN, DWAVE_ARN, @@ -1073,20 +1076,24 @@ def test_device_non_qpu_region_error(mock_copy_session): @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_no_extra(aws_quantum_task_mock, device, circuit): +def test_run_no_extra(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, device, circuit, + s3_destination_folder, + AwsDevice.DEFAULT_SHOTS_QPU, ) @patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") -def test_run_with_reservation_arn(aws_quantum_task_mock, device, circuit): +def test_run_with_reservation_arn(aws_quantum_task_mock, device, circuit, s3_destination_folder): _run_and_assert( aws_quantum_task_mock, device, circuit, + s3_destination_folder, + AwsDevice.DEFAULT_SHOTS_QPU, reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", ) @@ -1415,6 +1422,7 @@ def test_run_with_kwargs(aws_quantum_task_mock, device, circuit, s3_destination_ device, circuit, s3_destination_folder, + shots=AwsDevice.DEFAULT_SHOTS_QPU, extra_kwargs={"bar": 1, "baz": 2}, ) @@ -2259,3 +2267,16 @@ def test_run_batch_with_noise_model( expected_circuit = Circuit().h(0).bit_flip(0, 0.05).cnot(0, 1).two_qubit_depolarizing(0, 1, 0.1) assert aws_quantum_task_mock.call_args_list[0][0][2] == expected_circuit + + +@patch("braket.aws.aws_device.AwsSession") +@patch("braket.aws.aws_quantum_task.AwsQuantumTask.create") +def test_run_program_set_default_shots(aws_quantum_task_mock, aws_session_init, aws_session): + arn = RIGETTI_ARN + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 + device = AwsDevice(arn) + program_set = ProgramSet([Circuit().h(0).cnot(0, 1)]) + _ = device.run(program_set) + assert aws_quantum_task_mock.call_args_list[0][0][2] == program_set + assert aws_quantum_task_mock.call_args_list[0][0][4] == -1 diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index c7ae7ed69..bb1676c9b 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -19,6 +19,8 @@ from unittest.mock import MagicMock, Mock, patch import pytest +from botocore.exceptions import ClientError + from common_test_utils import MockS3 from jsonschema import validate @@ -49,17 +51,22 @@ from braket.error_mitigation.debias import Debias from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQASMProgram +from braket.parametric import FreeParameter +from braket.program_sets import CircuitBinding, ProgramSet from braket.pulse import Frame, Port, PulseSequence +from braket.schema_common import BraketSchemaBase from braket.tasks import ( AnalogHamiltonianSimulationQuantumTaskResult, AnnealingQuantumTaskResult, GateModelQuantumTaskResult, PhotonicModelQuantumTaskResult, + ProgramSetQuantumTaskResult, ) S3_TARGET = AwsSession.S3DestinationFolder("foo", "bar") IONQ_ARN = "device/qpu/ionq" +IQM_ARN = "device/qpu/iqm" RIGETTI_ARN = "device/qpu/rigetti" OQC_ARN = "device/qpu/oqc" SIMULATOR_ARN = "device/quantum-simulator" @@ -363,6 +370,25 @@ def test_result_circuit(circuit_task): ) +def test_result_program_set(circuit_task): + _mock_metadata(circuit_task._aws_session, "COMPLETED") + _mock_s3(circuit_task._aws_session, MockS3.MOCK_S3_RESULT_PROGRAM_SET) + + expected = ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(MockS3.MOCK_S3_RESULT_PROGRAM_SET) + ) + actual = circuit_task.result() + assert expected.task_metadata == actual.task_metadata + assert expected.programs == actual.programs + assert expected.num_executables == actual.num_executables + + s3_bucket = circuit_task.metadata()["outputS3Bucket"] + s3_object_key = circuit_task.metadata()["outputS3Directory"] + circuit_task._aws_session.retrieve_s3_object_body.assert_called_with( + s3_bucket, f"{s3_object_key}/results.json" + ) + + def test_result_annealing(annealing_task): _mock_metadata(annealing_task._aws_session, "COMPLETED") _mock_s3(annealing_task._aws_session, MockS3.MOCK_S3_RESULT_ANNEALING) @@ -923,6 +949,115 @@ def test_create_circuit_with_shots_value_error(aws_session, arn, circuit): AwsQuantumTask.create(aws_session, arn, circuit, S3_TARGET, 0) +def test_create_program_set(aws_session, arn): + circ1 = Circuit().h(0).cnot(0, 1) + circ2 = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet([circ1]) + ProgramSet( + CircuitBinding(circ2, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + ) + aws_session.create_quantum_task.return_value = arn + shots = 300 + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + IQM_ARN, + program_set.to_ir().json(), + S3_TARGET, + shots, + ) + + +def test_create_program_set_shots(aws_session, arn): + circ1 = Circuit().h(0).cnot(0, 1) + circ2 = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet([circ1], 100) + ProgramSet( + CircuitBinding(circ2, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + ) + aws_session.create_quantum_task.return_value = arn + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, -1) + + _assert_create_quantum_task_called_with( + aws_session, + IQM_ARN, + program_set.to_ir().json(), + S3_TARGET, + program_set.total_shots, + ) + + +def test_create_program_set_invalid_shots(aws_session, arn): + circ1 = Circuit().h(0).cnot(0, 1) + circ2 = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet([circ1]) + ProgramSet( + CircuitBinding(circ2, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + ) + with pytest.raises(ValueError): + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, -1) + + +def test_create_program_set_client_error(aws_session, arn): + program_set = ProgramSet([Circuit().h(0).cnot(0, 1)], 100) + aws_session.create_quantum_task.side_effect = ClientError( + { + "Error": {"Code": "ValidationException", "Message": "bar"}, + "message": "baz", + "programSetValidationFailures": [{"programIndex": 0, "errors": ["qux"]}], + }, + "foo", + ) + with pytest.raises( + ClientError, match="Rerun the task and catch the exception for more details" + ): + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, -1) + + +def test_create_program_set_client_error_no_program_set_validation_failures(aws_session, arn): + program_set = ProgramSet([Circuit().h(0).cnot(0, 1)], 100) + aws_session.create_quantum_task.side_effect = ClientError( + { + "Error": {"Code": "ValidationException", "Message": "bar"}, + "message": "baz", + }, + "foo", + ) + try: + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, -1) + except ClientError as e: + assert "Rerun the task" not in e.response["message"] + + +def test_create_ir_program_set(aws_session, arn): + circ1 = Circuit().h(0).cnot(0, 1) + circ2 = Circuit().rx(0, FreeParameter("theta")) + program_set = ( + ProgramSet([circ1], 100) + + ProgramSet(CircuitBinding(circ2, input_sets=[{"theta": 1.23}, {"theta": 3.21}])) + ).to_ir() + aws_session.create_quantum_task.return_value = arn + shots = 30 + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, shots) + + _assert_create_quantum_task_called_with( + aws_session, + IQM_ARN, + program_set.json(), + S3_TARGET, + shots, + ) + + +def test_create_ir_program_set_invalid_shots(aws_session, arn): + circ1 = Circuit().h(0).cnot(0, 1) + circ2 = Circuit().rx(0, FreeParameter("theta")) + program_set = ( + ProgramSet([circ1], 100) + + ProgramSet(CircuitBinding(circ2, input_sets=[{"theta": 1.23}, {"theta": 3.21}])) + ).to_ir() + with pytest.raises(ValueError): + AwsQuantumTask.create(aws_session, IQM_ARN, program_set, S3_TARGET, -1) + + @pytest.mark.parametrize( "device_parameters,arn", [ diff --git a/test/unit_tests/braket/circuits/noise/test_noise_model.py b/test/unit_tests/braket/circuits/noise/test_noise_model.py index d184aeb6b..e27ce5d3b 100644 --- a/test/unit_tests/braket/circuits/noise/test_noise_model.py +++ b/test/unit_tests/braket/circuits/noise/test_noise_model.py @@ -575,6 +575,7 @@ def test_apply_readout_noise_result_type_only(): for instr in noisy_circuit.instructions: assert not (isinstance(instr.operator, BitFlip) and instr.target == [1]) + def test_measurecriteria_for_circuit_with_observable_resulttype(): noise_model = NoiseModel() noise_model.add_noise(BitFlip(0.1), MeasureCriteria(qubits=[0])) @@ -582,4 +583,4 @@ def test_measurecriteria_for_circuit_with_observable_resulttype(): circ = Circuit().h(0).sample(observable=Observable.Z()) noisy_circuit = noise_model.apply(circ) - assert noisy_circuit == circ # no effect \ No newline at end of file + assert noisy_circuit == circ # no effect diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index a6ed047c6..4528b294c 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -633,13 +633,12 @@ def test_measure_verbatim_box(): source="\n".join([ "OPENQASM 3.0;", "bit[1] b;", - "qubit[2] q;", "#pragma braket verbatim", "box{", - "x q[0];", - "x q[1];", + "x $0;", + "x $1;", "}", - "b[0] = measure q[0];", + "b[0] = measure $0;", ]), inputs={}, ) @@ -1190,7 +1189,6 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): OpenQasmProgram( source="\n".join([ "OPENQASM 3.0;", - "qubit[5] q;", "cal {", " waveform drag_gauss_wf = drag_gaussian" + "(3.0ms, 400.0ms, 0.2, 1, false);", @@ -1203,10 +1201,10 @@ def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): " set_frequency(predefined_frame_1, 6000000.0);", " play(predefined_frame_1, drag_gauss_wf);", "}", - "rx(0.15) q[0];", - "rx(0.3) q[4];", - "#pragma braket noise bit_flip(0.2) q[3]", - "#pragma braket result expectation i(q[0])", + "rx(0.15) $0;", + "rx(0.3) $4;", + "#pragma braket noise bit_flip(0.2) $3", + "#pragma braket result expectation i($0)", ]), inputs={}, ), diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py index db0b70619..a05e4c20a 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -18,38 +18,34 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.serialization import IRType -testdata = [ - ( - compiler_directives.StartVerbatimBox, - ir.StartVerbatimBox, - "#pragma braket verbatim\nbox{", - compiler_directives.EndVerbatimBox, - ), - ( - compiler_directives.EndVerbatimBox, - ir.EndVerbatimBox, - "}", - compiler_directives.StartVerbatimBox, - ), -] - -@pytest.mark.parametrize("testclass,irclass,openqasm_str,counterpart", testdata) -def test_counterpart(testclass, irclass, openqasm_str, counterpart): - assert testclass().counterpart() == counterpart() - - -@pytest.mark.parametrize("testclass,irclass,openqasm_str,counterpart", testdata) -def test_to_ir(testclass, irclass, openqasm_str, counterpart): - assert testclass().to_ir(ir_type=IRType.JAQCD) == irclass() - assert testclass().to_ir(ir_type=IRType.OPENQASM) == openqasm_str - - -@pytest.mark.parametrize("testclass,irclass,openqasm_str,counterpart", testdata) -def test_equality(testclass, irclass, openqasm_str, counterpart): - op1 = testclass() - op2 = testclass() - assert op1 == op2 - assert op1 is not op2 - assert op1 != CompilerDirective(ascii_symbols=["foo"]) - assert op1 != "not a directive" +@pytest.mark.parametrize( + "testclass,irclass,openqasm_str,counterpart", + [ + ( + compiler_directives.StartVerbatimBox, + ir.StartVerbatimBox, + "#pragma braket verbatim\nbox{", + compiler_directives.EndVerbatimBox, + ), + ( + compiler_directives.EndVerbatimBox, + ir.EndVerbatimBox, + "}", + compiler_directives.StartVerbatimBox, + ), + ], +) +def test_verbatim(testclass, irclass, openqasm_str, counterpart): + directive = testclass() + assert directive.counterpart() == counterpart() + assert directive.requires_physical_qubits + + assert directive.to_ir(ir_type=IRType.JAQCD) == irclass() + assert directive.to_ir(ir_type=IRType.OPENQASM) == openqasm_str + + op = testclass() + assert directive == op + assert directive is not op + assert directive != CompilerDirective(ascii_symbols=["foo"]) + assert directive != "not a directive" diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 1ca2523c8..173881600 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -17,7 +17,8 @@ import numpy.testing as npt import pytest -from braket.circuits import Gate, Observable +from braket.circuits import gates, observables +from braket.circuits.observable import EULER_X_PREFIX, EULER_Z1_PREFIX, EULER_Z2_PREFIX from braket.circuits.observables import observable_from_ir from braket.circuits.quantum_operator_helpers import get_pauli_eigenvalues from braket.circuits.serialization import ( @@ -26,202 +27,169 @@ QubitReferenceType, ) -testdata = [ - (Observable.I(), Gate.I(), ["i"], (), np.array([1, 1])), - (Observable.X(), Gate.X(), ["x"], tuple([Gate.H()]), get_pauli_eigenvalues(1)), - ( - Observable.Y(), - Gate.Y(), - ["y"], - tuple([Gate.Z(), Gate.S(), Gate.H()]), - get_pauli_eigenvalues(1), - ), - (Observable.Z(), Gate.Z(), ["z"], (), get_pauli_eigenvalues(1)), - (Observable.H(), Gate.H(), ["h"], tuple([Gate.Ry(-math.pi / 4)]), get_pauli_eigenvalues(1)), -] - -invalid_hermitian_matrices = [ - (np.array([[1]])), - (np.array([1])), - (np.array([0, 1, 2])), - (np.array([[0, 1], [1, 2], [3, 4]])), - (np.array([[0, 1, 2], [2, 3]], dtype=object)), - (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), - (Gate.T().to_matrix()), -] - - -@pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata -) -def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - expected = expected_ir - actual = testobject.to_ir() - assert actual == expected - @pytest.mark.parametrize( "observable, observable_with_targets, serialization_properties, target, expected_ir", [ ( - Observable.I(), - Observable.I(3), + observables.I(), + observables.I(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "i(q[3])", ), ( - Observable.I(), - Observable.I(3), + observables.I(), + observables.I(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "i($3)", ), ( - Observable.I(), + observables.I(), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "i all", ), ( - Observable.X(), - Observable.X(3), + observables.X(), + observables.X(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "x(q[3])", ), ( - Observable.X(), - Observable.X(3), + observables.X(), + observables.X(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "x($3)", ), ( - Observable.X(), + observables.X(), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "x all", ), ( - Observable.Y(), - Observable.Y(3), + observables.Y(), + observables.Y(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "y(q[3])", ), ( - Observable.Y(), - Observable.Y(3), + observables.Y(), + observables.Y(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "y($3)", ), ( - Observable.Y(), + observables.Y(), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "y all", ), ( - Observable.Z(), - Observable.Z(3), + observables.Z(), + observables.Z(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "z(q[3])", ), ( - Observable.Z(), - Observable.Z(3), + observables.Z(), + observables.Z(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "z($3)", ), ( - Observable.Z(), + observables.Z(), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "z all", ), ( - Observable.H(), - Observable.H(3), + observables.H(), + observables.H(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3], "h(q[3])", ), ( - Observable.H(), - Observable.H(3), + observables.H(), + observables.H(3), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "h($3)", ), ( - Observable.H(), + observables.H(), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "h all", ), ( - Observable.Hermitian(np.eye(4)), - Observable.Hermitian(np.eye(4), targets=[1, 2]), + observables.Hermitian(np.eye(4)), + observables.Hermitian(np.eye(4), targets=[1, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [1, 2], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) q[1], q[2]", ), ( - Observable.Hermitian(np.eye(4)), - Observable.Hermitian(np.eye(4), targets=[1, 2]), + observables.Hermitian(np.eye(4)), + observables.Hermitian(np.eye(4), targets=[1, 2]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [1, 2], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) $1, $2", ), ( - Observable.Hermitian(np.eye(2)), + observables.Hermitian(np.eye(2)), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), None, "hermitian([[1+0im, 0im], [0im, 1+0im]]) all", ), ( - Observable.H() @ Observable.Z(), - Observable.H(3) @ Observable.Z(0), + observables.H() @ observables.Z(), + observables.H(3) @ observables.Z(0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0], "h(q[3]) @ z(q[0])", ), ( - Observable.H() @ Observable.Z(), - Observable.H(3) @ Observable.Z(0), + observables.H() @ observables.Z(), + observables.H(3) @ observables.Z(0), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0], "h($3) @ z($0)", ), ( - Observable.H() @ Observable.Z() @ Observable.I(), - Observable.H(3) @ Observable.Z(0) @ Observable.I(1), + observables.H() @ observables.Z() @ observables.I(), + observables.H(3) @ observables.Z(0) @ observables.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "h(q[3]) @ z(q[0]) @ i(q[1])", ), ( - Observable.H() @ Observable.Z() @ Observable.I(), - Observable.H(3) @ Observable.Z(0) @ Observable.I(1), + observables.H() @ observables.Z() @ observables.I(), + observables.H(3) @ observables.Z(0) @ observables.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "h($3) @ z($0) @ i($1)", ), ( - Observable.Hermitian(np.eye(4)) @ Observable.I(), - Observable.Hermitian(np.eye(4), targets=[3, 0]) @ Observable.I(1), + observables.Hermitian(np.eye(4)) @ observables.I(), + observables.Hermitian(np.eye(4), targets=[3, 0]) @ observables.I(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), [3, 0, 1], "hermitian([[1+0im, 0im, 0im, 0im], [0im, 1+0im, 0im, 0im], " @@ -229,8 +197,8 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv " @ i(q[1])", ), ( - Observable.I() @ Observable.Hermitian(np.eye(4)), - Observable.I(3) @ Observable.Hermitian(np.eye(4), targets=[0, 1]), + observables.I() @ observables.Hermitian(np.eye(4)), + observables.I(3) @ observables.Hermitian(np.eye(4), targets=[0, 1]), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "i($3) @ " @@ -238,15 +206,15 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) $0, $1", ), ( - 3 * (2 * Observable.Z()), - 3 * (2 * Observable.Z(3)), + 3 * (2 * observables.Z()), + 3 * (2 * observables.Z(3)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3], "6 * z($3)", ), ( - (2 * Observable.I()) @ (2 * Observable.Hermitian(np.eye(4))), - (2 * Observable.I(3)) @ (2 * Observable.Hermitian(np.eye(4), targets=[0, 1])), + (2 * observables.I()) @ (2 * observables.Hermitian(np.eye(4))), + (2 * observables.I(3)) @ (2 * observables.Hermitian(np.eye(4), targets=[0, 1])), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [3, 0, 1], "4 * i($3) @ " @@ -254,58 +222,60 @@ def test_to_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenv "[0im, 0im, 1+0im, 0im], [0im, 0im, 0im, 1+0im]]) $0, $1", ), ( - Observable.Z() + 2 * Observable.H(), - Observable.Z(3) + 2 * Observable.H(4), + observables.Z() + 2 * observables.H(), + observables.Z(3) + 2 * observables.H(4), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [4]], "z($3) + 2 * h($4)", ), ( - 3 * (Observable.H() + 2 * Observable.X()), - 3 * (Observable.H(3) + 2 * Observable.X(0)), + 3 * (observables.H() + 2 * observables.X()), + 3 * (observables.H(3) + 2 * observables.X(0)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [0]], "3 * h($3) + 6 * x($0)", ), ( - 3 * (Observable.H() + 2 * Observable.H()), - 3 * (Observable.H(3) + 2 * Observable.H(3)), + 3 * (observables.H() + 2 * observables.H()), + 3 * (observables.H(3) + 2 * observables.H(3)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [3]], "3 * h($3) + 6 * h($3)", ), ( - 3 * (Observable.H() + 2 * Observable.H()), - 3 * (Observable.H(3) + 2 * Observable.H(5)), + 3 * (observables.H() + 2 * observables.H()), + 3 * (observables.H(3) + 2 * observables.H(5)), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[3], [5]], "3 * h($3) + 6 * h($5)", ), ( - (2 * Observable.Y()) @ (3 * Observable.I()) + 0.75 * Observable.Y() @ Observable.Z(), - (2 * Observable.Y(0)) @ (3 * Observable.I(1)) - + 0.75 * Observable.Y(0) @ Observable.Z(1), + (2 * observables.Y()) @ (3 * observables.I()) + + 0.75 * observables.Y() @ observables.Z(), + (2 * observables.Y(0)) @ (3 * observables.I(1)) + + 0.75 * observables.Y(0) @ observables.Z(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0, 1], [0, 1]], "6 * y($0) @ i($1) + 0.75 * y($0) @ z($1)", ), ( - (-2 * Observable.Y()) @ (3 * Observable.I()) + -0.75 * Observable.Y() @ Observable.Z(), - (-2 * Observable.Y(0)) @ (3 * Observable.I(1)) - + -0.75 * Observable.Y(0) @ Observable.Z(1), + (-2 * observables.Y()) @ (3 * observables.I()) + + -0.75 * observables.Y() @ observables.Z(), + (-2 * observables.Y(0)) @ (3 * observables.I(1)) + + -0.75 * observables.Y(0) @ observables.Z(1), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0, 1], [0, 1]], "-6 * y($0) @ i($1) - 0.75 * y($0) @ z($1)", ), ( - 4 * (2 * Observable.Z() + 2 * (3 * Observable.X() @ (2 * Observable.Y()))), - 4 * (2 * Observable.Z(0) + 2 * (3 * Observable.X(1) @ (2 * Observable.Y(2)))), + 4 * (2 * observables.Z() + 2 * (3 * observables.X() @ (2 * observables.Y()))), + 4 * (2 * observables.Z(0) + 2 * (3 * observables.X(1) @ (2 * observables.Y(2)))), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[0], [1, 2]], "8 * z($0) + 48 * x($1) @ y($2)", ), ( - 4 * (2 * Observable.Z(0) + 2 * (3 * Observable.X(1) @ (2 * Observable.Y(2)))), + 4 * (2 * observables.Z(0) + 2 * (3 * observables.X(1) @ (2 * observables.Y(2)))), None, OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), [[5], [4, 3]], @@ -338,13 +308,13 @@ def test_observables_to_ir_openqasm( @pytest.mark.parametrize( "observable", [ - 2 * Observable.H(), - 3 * Observable.Z(), - 2 * Observable.I(), - 3 * Observable.X(), - 2 * Observable.Y(), - 2 * Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])), - 2 * Observable.TensorProduct([Observable.Z(), Observable.H()]), + 2 * observables.H(), + 3 * observables.Z(), + 2 * observables.I(), + 3 * observables.X(), + 2 * observables.Y(), + 2 * observables.Hermitian(matrix=np.array([[0, 1], [1, 0]])), + 2 * observables.TensorProduct([observables.Z(), observables.H()]), ], ) def test_observable_coef_jaqcd(observable): @@ -356,12 +326,12 @@ def test_observable_coef_jaqcd(observable): @pytest.mark.parametrize( "expression, observable", [ - ([], Observable.X()), - ([2], Observable.Y()), - ([2, "invalid_str"], Observable.Z()), - ([2.0], Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]]))), - ([2], Observable.Sum([Observable.X() + Observable.Y()])), - ([2], Observable.Y() + 0.75 * Observable.Y() @ Observable.Z()), + ([], observables.X()), + ([2], observables.Y()), + ([2, "invalid_str"], observables.Z()), + ([2.0], observables.Hermitian(matrix=np.array([[0, 1], [1, 0]]))), + ([2], observables.Sum([observables.X() + observables.Y()])), + ([2], observables.Y() + 0.75 * observables.Y() @ observables.Z()), ], ) def test_invalid_scalar_multiplication(expression, observable): @@ -373,34 +343,34 @@ def test_invalid_scalar_multiplication(expression, observable): "observable, matrix", [ ( - (-3 * Observable.H()).to_matrix(), + (-3 * observables.H()).to_matrix(), np.array([ [-2.12132034 + 0.0j, -2.12132034 + 0.0j], [-2.12132034 + 0.0j, 2.12132034 - 0.0j], ]), ), ( - (3 * Observable.Z()).to_matrix(), + (3 * observables.Z()).to_matrix(), np.array([[3.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, -3.0 + 0.0j]]), ), ( - (2 * Observable.I()).to_matrix(), + (2 * observables.I()).to_matrix(), np.array([[2.0 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 2.0 + 0.0j]]), ), ( - (1.2 * Observable.X()).to_matrix(), + (1.2 * observables.X()).to_matrix(), np.array([[0.0 + 0.0j, 1.2 + 0.0j], [1.2 + 0.0j, 0.0 + 0.0j]]), ), ( - (1e-2 * Observable.Y()).to_matrix(), + (1e-2 * observables.Y()).to_matrix(), np.array([[0.0 + 0.0j, 0.0 - 0.01j], [0 + 0.01j, 0.0 + 0.0j]]), ), ( - (np.array(1.3) * Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]]))).to_matrix(), + (np.array(1.3) * observables.Hermitian(matrix=np.array([[0, 1], [1, 0]]))).to_matrix(), np.array([[0.0 + 0.0j, 1.3 + 0.0j], [1.3 + 0.0j, 0.0 + 0.0j]]), ), ( - (2 * Observable.TensorProduct([Observable.Z(), Observable.H()])).to_matrix(), + (2 * observables.TensorProduct([observables.Z(), observables.H()])).to_matrix(), np.array( [ [1.41421356 + 0.0j, 1.41421356 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j], @@ -419,9 +389,9 @@ def test_valid_scaled_matrix(observable, matrix): @pytest.mark.parametrize( "observable, eigenvalue", [ - (-2 * Observable.I().eigenvalues, np.array([-2.0, -2.0])), + (-2 * observables.I().eigenvalues, np.array([-2.0, -2.0])), ( - 3e-2 * Observable.Hermitian(matrix=np.array([[0, 1], [1, 0]])).eigenvalues, + 3e-2 * observables.Hermitian(matrix=np.array([[0, 1], [1, 0]])).eigenvalues, np.array([-0.03, 0.03]), ), ], @@ -431,53 +401,113 @@ def test_valid_scaled_eigenvalues(observable, eigenvalue): @pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata + "observable,gate,expected_ir,basis_rotation_gates,eigenvalues", + [ + (observables.I(), gates.I(), ["i"], (), np.array([1, 1])), + (observables.X(), gates.X(), ["x"], (gates.H(),), get_pauli_eigenvalues(1)), + ( + observables.Y(), + gates.Y(), + ["y"], + (gates.Z(), gates.S(), gates.H()), + get_pauli_eigenvalues(1), + ), + (observables.Z(), gates.Z(), ["z"], (), get_pauli_eigenvalues(1)), + (observables.H(), gates.H(), ["h"], (gates.Ry(-math.pi / 4),), get_pauli_eigenvalues(1)), + ], ) -def test_gate_equality(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - assert testobject.qubit_count == gateobject.qubit_count - assert testobject.ascii_symbols == gateobject.ascii_symbols - assert testobject.matrix_equivalence(gateobject) - assert testobject.basis_rotation_gates == basis_rotation_gates - assert np.allclose(testobject.eigenvalues, eigenvalues) +def test_unitary_observables(observable, gate, expected_ir, basis_rotation_gates, eigenvalues): + expected = expected_ir + actual = observable.to_ir() + assert actual == expected + assert observable == observable_from_ir(expected_ir) + assert observable.qubit_count == gate.qubit_count + assert observable.ascii_symbols == gate.ascii_symbols + assert observable.matrix_equivalence(gate) + assert observable.basis_rotation_gates == basis_rotation_gates + compare_eigenvalues(observable, eigenvalues) -@pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata -) -def test_basis_rotation_gates( - testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues -): - assert testobject.basis_rotation_gates == basis_rotation_gates +@pytest.mark.parametrize("observable", [observables.I(0), observables.Z(0)]) +def test_euler_angles_no_rotation(observable): + euler_angles = observable.euler_angles + assert np.allclose( + np.linalg.multi_dot([ + gates.Rz(euler_angles[f"{EULER_Z2_PREFIX}0"]).to_matrix(), + gates.Rx(euler_angles[f"{EULER_X_PREFIX}0"]).to_matrix(), + gates.Rz(euler_angles[f"{EULER_Z1_PREFIX}0"]).to_matrix(), + ]), + np.eye(2), + ) -@pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata -) -def test_eigenvalues(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - compare_eigenvalues(testobject, eigenvalues) + +@pytest.mark.parametrize("observable,global_phase", [(observables.X(0), -1j)]) +def test_euler_angles_one_rotation_gate(observable, global_phase): + euler_angles = observable.euler_angles + assert np.allclose( + np.linalg.multi_dot([ + gates.Rz(euler_angles[f"{EULER_Z2_PREFIX}0"]).to_matrix(), + gates.Rx(euler_angles[f"{EULER_X_PREFIX}0"]).to_matrix(), + gates.Rz(euler_angles[f"{EULER_Z1_PREFIX}0"]).to_matrix(), + ]), + observable.basis_rotation_gates[0].to_matrix() * global_phase, + ) + + +@pytest.mark.parametrize("observable,global_phase", [(observables.Y(0), (1 - 1j) / np.sqrt(2))]) +def test_euler_angles_multiple_rotation_gates(observable, global_phase): + euler_angles = observable.euler_angles + assert np.allclose( + np.linalg.multi_dot([ + gates.Rz(euler_angles[f"{EULER_Z2_PREFIX}0"]).to_matrix(), + gates.Rx(euler_angles[f"{EULER_X_PREFIX}0"]).to_matrix(), + gates.Rz(euler_angles[f"{EULER_Z1_PREFIX}0"]).to_matrix(), + ]), + np.linalg.multi_dot( + list(reversed([gate.to_matrix() for gate in observable.basis_rotation_gates])) + ) + * global_phase, + ) @pytest.mark.parametrize( - "testobject,gateobject,expected_ir,basis_rotation_gates,eigenvalues", testdata + "observable", + [ + observables.H(0), + observables.Hermitian(matrix=np.array([[1.0, 0.0], [0.0, 1.0]]), targets=[0]), + ], ) -def test_observable_from_ir(testobject, gateobject, expected_ir, basis_rotation_gates, eigenvalues): - assert testobject == observable_from_ir(expected_ir) +def test_euler_angles_unsupported(observable): + with pytest.raises(NotImplementedError): + observable.euler_angles # Hermitian -@pytest.mark.parametrize("matrix", invalid_hermitian_matrices) +@pytest.mark.parametrize( + "matrix", + [ + (np.array([[1]])), + (np.array([1])), + (np.array([0, 1, 2])), + (np.array([[0, 1], [1, 2], [3, 4]])), + (np.array([[0, 1, 2], [2, 3]], dtype=object)), + (np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])), + (gates.T().to_matrix()), + ], +) def test_hermitian_invalid_matrix(matrix): with pytest.raises(ValueError): - Observable.Hermitian(matrix=matrix) + observables.Hermitian(matrix=matrix) def test_hermitian_equality(): - matrix = Observable.H().to_matrix() - a1 = Observable.Hermitian(matrix=matrix) - a2 = Observable.Hermitian(matrix=matrix) - a3 = Observable.Hermitian(matrix=Observable.I().to_matrix()) + matrix = observables.H().to_matrix() + a1 = observables.Hermitian(matrix=matrix) + a2 = observables.Hermitian(matrix=matrix) + a3 = observables.Hermitian(matrix=observables.I().to_matrix()) a4 = "hi" assert a1 == a2 assert a1 != a3 @@ -485,8 +515,8 @@ def test_hermitian_equality(): def test_hermitian_to_ir(): - matrix = Observable.I().to_matrix() - obs = Observable.Hermitian(matrix=matrix) + matrix = observables.I().to_matrix() + obs = observables.Hermitian(matrix=matrix) assert obs.to_ir() == [[[[1, 0], [0, 0]], [[0, 0], [1, 0]]]] @@ -499,23 +529,23 @@ def test_hermitian_to_ir(): ], ) def test_hermitian_eigenvalues(matrix, eigenvalues): - compare_eigenvalues(Observable.Hermitian(matrix=matrix), eigenvalues) + compare_eigenvalues(observables.Hermitian(matrix=matrix), eigenvalues) def test_hermitian_matrix_target_mismatch(): with pytest.raises(ValueError): - Observable.Hermitian(np.eye(4), targets=[0, 1, 2]) + observables.Hermitian(np.eye(4), targets=[0, 1, 2]) def test_flattened_tensor_product(): - observable_one = Observable.Z() @ Observable.Y() - observable_two = Observable.X() @ Observable.H() - actual = Observable.TensorProduct([observable_one, observable_two]) - expected = Observable.TensorProduct([ - Observable.Z(), - Observable.Y(), - Observable.X(), - Observable.H(), + observable_one = observables.Z() @ observables.Y() + observable_two = observables.X() @ observables.H() + actual = observables.TensorProduct([observable_one, observable_two]) + expected = observables.TensorProduct([ + observables.Z(), + observables.Y(), + observables.X(), + observables.H(), ]) assert expected == actual @@ -546,8 +576,8 @@ def test_flattened_tensor_product(): ], ) def test_hermitian_basis_rotation_gates(matrix, basis_rotation_matrix): - expected_unitary = Gate.Unitary(matrix=basis_rotation_matrix) - actual_rotation_gates = Observable.Hermitian(matrix=matrix).basis_rotation_gates + expected_unitary = gates.Unitary(matrix=basis_rotation_matrix) + actual_rotation_gates = observables.Hermitian(matrix=matrix).basis_rotation_gates assert actual_rotation_gates == (expected_unitary,) assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) @@ -561,12 +591,12 @@ def test_observable_from_ir_hermitian_value_error(): def test_observable_from_ir_hermitian(): ir_observable = [[[[1, 0], [0, 0]], [[0, 0], [1, 0]]]] actual_observable = observable_from_ir(ir_observable) - assert actual_observable == Observable.Hermitian(matrix=np.array([[1.0, 0.0], [0.0, 1.0]])) + assert actual_observable == observables.Hermitian(matrix=np.array([[1.0, 0.0], [0.0, 1.0]])) def test_hermitian_str(): assert ( - str(Observable.Hermitian(matrix=np.array([[1.0, 0.0], [0.0, 1.0]]))) + str(observables.Hermitian(matrix=np.array([[1.0, 0.0], [0.0, 1.0]]))) == "Hermitian('qubit_count': 1, 'matrix': [[1.+0.j 0.+0.j], [0.+0.j 1.+0.j]])" ) @@ -575,17 +605,28 @@ def test_hermitian_str(): def test_tensor_product_to_ir(): - t = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + t = observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) assert t.to_ir() == ["z", "i", "x"] assert t.qubit_count == 3 assert t.ascii_symbols == tuple(["Z@I@X"] * 3) +def test_tensor_product_euler_angles(): + z0 = observables.Z(0) + y2 = observables.Y(2) + x5 = observables.X(5) + expected = {} + expected.update(z0.euler_angles) + expected.update(y2.euler_angles) + expected.update(x5.euler_angles) + assert (z0 @ y2 @ x5).euler_angles == expected + + def test_tensor_product_matmul_tensor(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - t2 = Observable.TensorProduct([ - Observable.Hermitian(matrix=Observable.I().to_matrix()), - Observable.Y(), + t1 = observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) + t2 = observables.TensorProduct([ + observables.Hermitian(matrix=observables.I().to_matrix()), + observables.Y(), ]) t3 = t1 @ t2 assert t3.to_ir() == ["z", "i", "x", [[[1.0, 0], [0, 0]], [[0, 0], [1.0, 0]]], "y"] @@ -594,8 +635,8 @@ def test_tensor_product_matmul_tensor(): def test_tensor_product_matmul_observable(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - o1 = Observable.I() + t1 = observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) + o1 = observables.I() t = t1 @ o1 assert t.to_ir() == ["z", "i", "x", "i"] assert t.qubit_count == 4 @@ -603,19 +644,19 @@ def test_tensor_product_matmul_observable(): def test_tensor_product_eigenvalue_index_out_of_bounds(): - obs = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + obs = observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) with pytest.raises(ValueError): obs.eigenvalue(8) def test_tensor_product_value_error(): with pytest.raises(TypeError): - Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) @ "a" + observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) @ "a" def test_tensor_product_rmatmul_observable(): - t1 = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) - o1 = Observable.I() + t1 = observables.TensorProduct([observables.Z(), observables.I(), observables.X()]) + o1 = observables.I() t = o1 @ t1 assert t.to_ir() == ["i", "z", "i", "x"] assert t.qubit_count == 4 @@ -625,15 +666,21 @@ def test_tensor_product_rmatmul_observable(): @pytest.mark.parametrize( "observable,eigenvalues", [ - (Observable.X() @ Observable.Y(), np.array([1, -1, -1, 1])), - (Observable.X() @ Observable.Y() @ Observable.Z(), np.array([1, -1, -1, 1, -1, 1, 1, -1])), - (Observable.X() @ Observable.Y() @ Observable.I(), np.array([1, 1, -1, -1, -1, -1, 1, 1])), + (observables.X() @ observables.Y(), np.array([1, -1, -1, 1])), ( - Observable.X() - @ Observable.Hermitian( + observables.X() @ observables.Y() @ observables.Z(), + np.array([1, -1, -1, 1, -1, 1, 1, -1]), + ), + ( + observables.X() @ observables.Y() @ observables.I(), + np.array([1, 1, -1, -1, -1, -1, 1, 1]), + ), + ( + observables.X() + @ observables.Hermitian( np.array([[-1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) ) - @ Observable.Y(), + @ observables.Y(), np.array([-1, 1, -1, 1, 1, -1, 1, -1, 1, -1, 1, -1, -1, 1, -1, 1]), ), ], @@ -648,16 +695,16 @@ def test_tensor_product_eigenvalues(observable, eigenvalues): @pytest.mark.parametrize( "observable,basis_rotation_gates", [ - (Observable.X() @ Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H())), + (observables.X() @ observables.Y(), (gates.H(), gates.Z(), gates.S(), gates.H())), ( - Observable.X() @ Observable.Y() @ Observable.Z(), - (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), + observables.X() @ observables.Y() @ observables.Z(), + (gates.H(), gates.Z(), gates.S(), gates.H()), ), ( - Observable.X() @ Observable.Y() @ Observable.I(), - (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), + observables.X() @ observables.Y() @ observables.I(), + (gates.H(), gates.Z(), gates.S(), gates.H()), ), - (Observable.X() @ Observable.H(), (Gate.H(), Gate.Ry(-np.pi / 4))), + (observables.X() @ observables.H(), (gates.H(), gates.Ry(-np.pi / 4))), ], ) def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): @@ -666,16 +713,20 @@ def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): def test_tensor_product_repeated_qubits(): with pytest.raises(ValueError): - (2 * Observable.Z(3)) @ (3 * Observable.H(3)) + (2 * observables.Z(3)) @ (3 * observables.H(3)) def test_tensor_product_with_and_without_targets(): with pytest.raises(ValueError): - (2 * Observable.Z(3)) @ (3 * Observable.H()) + (2 * observables.Z(3)) @ (3 * observables.H()) def test_observable_from_ir_tensor_product(): - expected_observable = Observable.TensorProduct([Observable.Z(), Observable.I(), Observable.X()]) + expected_observable = observables.TensorProduct([ + observables.Z(), + observables.I(), + observables.X(), + ]) actual_observable = observable_from_ir(["z", "i", "x"]) assert expected_observable == actual_observable @@ -696,7 +747,7 @@ def compare_eigenvalues(observable, expected): def test_sum_not_allowed_in_tensor_product(): sum_not_allowed_in_tensor_product = "Sum observables not allowed in TensorProduct" with pytest.raises(TypeError, match=sum_not_allowed_in_tensor_product): - Observable.TensorProduct([Observable.X() + Observable.Y()]) + observables.TensorProduct([observables.X() + observables.Y()]) # Sum of observables @@ -704,7 +755,7 @@ def test_sum_not_allowed_in_tensor_product(): @pytest.mark.parametrize( "observable,basis_rotation_gates", - [(Observable.X() + Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H()))], + [(observables.X() + observables.Y(), (gates.H(), gates.Z(), gates.S(), gates.H()))], ) def test_no_basis_rotation_support_for_sum(observable, basis_rotation_gates): no_basis_rotation_support_for_sum = "Basis rotation calculation not supported for Sum" @@ -715,18 +766,18 @@ def test_no_basis_rotation_support_for_sum(observable, basis_rotation_gates): def test_no_eigenvalues_support_for_sum(): no_eigen_value_support = "Eigenvalue calculation not supported for Sum" with pytest.raises(NotImplementedError, match=no_eigen_value_support): - (Observable.X() + Observable.Y()).eigenvalues + (observables.X() + observables.Y()).eigenvalues def test_matrix_not_supported_for_sum(): matrix_not_supported = "Matrix operation is not supported for Sum" with pytest.raises(NotImplementedError, match=matrix_not_supported): - (Observable.X() + Observable.Y()).to_matrix() + (observables.X() + observables.Y()).to_matrix() def test_invalid_targets_config_for_sum_obs(): observable, serialization_properties = ( - 2 * Observable.X() @ Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), + 2 * observables.X() @ observables.Y() + 0.75 * observables.Y() @ observables.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), ) target = [[0, 1]] @@ -741,16 +792,16 @@ def test_invalid_targets_config_for_sum_obs(): def test_sum_obs_str(): assert ( - str(Observable.Sum([2 * Observable.X() + 3 * Observable.Y()])) + str(observables.Sum([2 * observables.X() + 3 * observables.Y()])) == "Sum(X('qubit_count': 1), Y('qubit_count': 1))" ) def test_str_equality_sum_obs(): - t1 = Observable.Sum([2 * Observable.X() + 3 * Observable.Y()]) - t2 = Observable.Sum([2 * Observable.X() + 3 * Observable.Y()]) - t3 = Observable.Sum([2 * Observable.Z() + 3 * Observable.H()]) - t4 = Observable.Sum([Observable.Z() + Observable.H()]) + t1 = observables.Sum([2 * observables.X() + 3 * observables.Y()]) + t2 = observables.Sum([2 * observables.X() + 3 * observables.Y()]) + t3 = observables.Sum([2 * observables.Z() + 3 * observables.H()]) + t4 = observables.Sum([observables.Z() + observables.H()]) assert t1 == t2 assert t2 != t3 assert t1 != t3 @@ -759,7 +810,7 @@ def test_str_equality_sum_obs(): def test_invalid_target_length_for_sum_obs_term(): observable, serialization_properties = ( - 2 * Observable.Y() + 0.75 * Observable.Y() @ Observable.Z(), + 2 * observables.Y() + 0.75 * observables.Y() @ observables.Z(), OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), ) target = [[0, 1], [0, 1]] @@ -773,11 +824,11 @@ def test_invalid_target_length_for_sum_obs_term(): def test_unscaled_tensor_product(): - observable = 3 * ((2 * Observable.X()) @ (5 * Observable.Y())) - assert observable == 30 * (Observable.X() @ Observable.Y()) - assert observable._unscaled() == Observable.X() @ Observable.Y() + observable = 3 * ((2 * observables.X()) @ (5 * observables.Y())) + assert observable == 30 * (observables.X() @ observables.Y()) + assert observable._unscaled() == observables.X() @ observables.Y() def test_sum_with_and_without_targets(): with pytest.raises(ValueError): - Observable.X() + 3 * Observable.Y(4) + observables.X() + 3 * observables.Y(4) diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 39ad1c299..88a71cfa8 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -12,6 +12,7 @@ # language governing permissions and limitations under the License. import json +import math import textwrap import warnings from typing import Any, Optional @@ -32,12 +33,19 @@ from braket.device_schema.openqasm_device_action_properties import OpenQASMDeviceActionProperties from braket.devices import LocalSimulator, local_simulator from braket.ir.openqasm import Program +from braket.ir.openqasm.program_set_v1 import ProgramSet as OpenQASMProgramSet +from braket.program_sets import ProgramSet +from braket.program_sets.circuit_binding import CircuitBinding from braket.simulator import BraketSimulator -from braket.task_result import AnnealingTaskResult, GateModelTaskResult +from braket.task_result import AnnealingTaskResult, GateModelTaskResult, ProgramSetTaskResult from braket.task_result.analog_hamiltonian_simulation_task_result_v1 import ( AnalogHamiltonianSimulationTaskResult, ) -from braket.tasks import AnnealingQuantumTaskResult, GateModelQuantumTaskResult +from braket.tasks import ( + AnnealingQuantumTaskResult, + GateModelQuantumTaskResult, + ProgramSetQuantumTaskResult, +) from braket.tasks.analog_hamiltonian_simulation_quantum_task_result import ( AnalogHamiltonianSimulationQuantumTaskResult, ) @@ -56,6 +64,111 @@ "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, "instructions": [{"control": 0, "target": 1, "type": "cnot"}], }, + "additionalMetadata": { + "action": { + "braketSchemaHeader": {"name": "braket.ir.jaqcd.program", "version": "1"}, + "instructions": [{"control": 0, "target": 1, "type": "cnot"}], + }, + }, + }, +}) + +PROGRAM_SET_RESULT = ProgramSetTaskResult(**{ + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": [ + { + "braketSchemaHeader": {"name": "braket.task_result.program_result", "version": "1"}, + "executableResults": [ + { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_result", + "version": "1", + }, + "measurements": [ + [0, 0], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [0, 0], + [0, 0], + [1, 1], + [1, 1], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + ], + "measuredQubits": [0, 1], + "inputsIndex": 0, + }, + { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_failure", + "version": "1", + }, + "inputsIndex": 0, + "failureMetadata": { + "failureReason": "QPU was sick, should be good again after getting some sleep", + "retryable": True, + "category": "DEVICE", + }, + }, + ], + "source": { + "braketSchemaHeader": {"name": "braket.ir.openqasm.program", "version": "1"}, + "source": "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\nh q[0];\ncnot q[0], q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];", # noqa + "inputs": {"theta": [0.12, 2.1]}, + }, + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 50, + } + }, + } + ], + "taskMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_metadata", + "version": "1", + }, + "id": "arn:aws:braket:us-west-2:667256736152:quantum-task/bfebc86f-e4ed-4d6f-8131-addd1a49d6dc", # noqa + "deviceId": "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + "requestedShots": 120, + "successfulShots": 100, + "programMetadata": [{"executables": [{}]}], + "deviceParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.simulators.gate_model_simulator_device_parameters", + "version": "1", + }, + "paradigmParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + }, + "qubitCount": 5, + "disableQubitRewiring": False, + }, + }, + "createdAt": "2024-10-15T19:06:58.986Z", + "endedAt": "2024-10-15T19:07:00.382Z", + "status": "COMPLETED", + "totalFailedExecutables": 1, }, }) @@ -242,6 +355,15 @@ def properties(self) -> DeviceCapabilities: return device_properties +class DummyProgramSetSimulator(DummyProgramSimulator): + def run( + self, + program_set: OpenQASMProgramSet, + shots: int, + ) -> ProgramSetTaskResult: + return PROGRAM_SET_RESULT + + class DummySerializableProgram(SerializableProgram): def __init__(self, source: str): self.source = source @@ -783,3 +905,151 @@ def test_run_openqasm_with_noise_model(mock_run, noise_model): shots=4, ) assert w[-1].message.__str__() == expected_warning + + +@pytest.fixture +def program_set(): + circ1 = Circuit().x(0).x(1) + circ2 = Circuit().z(0).z(1) + return ProgramSet([circ1, circ2]) + + +@pytest.fixture +def program_set_ir(): + return OpenQASMProgramSet( + programs=[ + Program( + source=( + "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\n" + "x q[0];\nx q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];" + ), + inputs={}, + ), + Program( + source=( + "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\n" + "z q[0];\nz q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];" + ), + inputs={}, + ), + ], + ) + + +@patch.object(LocalSimulator, "_to_result_object") +@patch.object(DummyProgramSetSimulator, "run") +def test_run_program_set(mock_run, mock_to_result_object, program_set, program_set_ir): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + device.run(program_set, shots=10) + expected_program_set_ir = program_set_ir + mock_run.assert_called_with( + expected_program_set_ir, + shots=10, + ) + + +@patch.object(LocalSimulator, "_to_result_object") +@patch.object(DummyProgramSetSimulator, "run") +def test_run_program_set_with_inputs(mock_run, mock_to_result_object): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + circuit = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet( + CircuitBinding(circuit, input_sets=[{"theta": math.pi}, {"theta": 2 * math.pi}]) + ) + + device.run(program_set, shots=10) + expected_program_set_ir = OpenQASMProgramSet( + programs=[ + Program( + source=( + "OPENQASM 3.0;\ninput float theta;\nbit[1] b;\nqubit[1] q;\n" + "rx(theta) q[0];\nb[0] = measure q[0];" + ), + inputs={"theta": [3.141592653589793, 6.283185307179586]}, + ), + ], + ) + mock_run.assert_called_with( + expected_program_set_ir, + shots=10, + ) + + +@patch.object(LocalSimulator, "_to_result_object") +@patch.object(DummyProgramSetSimulator, "run") +def test_run_program_set_shots(mock_run, mock_to_result_object): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + circuit = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet( + CircuitBinding(circuit, input_sets=[{"theta": math.pi}, {"theta": 2 * math.pi}]), + shots_per_executable=10, + ) + + device.run(program_set) + expected_program_set_ir = OpenQASMProgramSet( + programs=[ + Program( + source=( + "OPENQASM 3.0;\ninput float theta;\nbit[1] b;\nqubit[1] q;\n" + "rx(theta) q[0];\nb[0] = measure q[0];" + ), + inputs={"theta": [3.141592653589793, 6.283185307179586]}, + ), + ], + ) + mock_run.assert_called_with( + expected_program_set_ir, + shots=20, + ) + + +def test_run_program_set_invalid_shots(): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + circuit = Circuit().rx(0, FreeParameter("theta")) + program_set = ProgramSet( + CircuitBinding(circuit, input_sets=[{"theta": math.pi}, {"theta": 2 * math.pi}]) + ) + + with pytest.raises(ValueError): + device.run(program_set) + + +@patch.object(LocalSimulator, "_to_result_object") +@patch.object(DummyProgramSetSimulator, "run") +def test_run_program_set_ir(mock_run, mock_to_result_object, program_set_ir): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + + device.run(program_set_ir, shots=10) + mock_run.assert_called_with( + program_set_ir, + shots=10, + ) + + +def test_program_set_result(program_set_ir): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + expected = ProgramSetQuantumTaskResult.from_object(PROGRAM_SET_RESULT) + actual = device.run(program_set_ir, shots=10).result() + assert expected.task_metadata == actual.task_metadata + assert expected.programs == actual.programs + assert expected.num_executables == actual.num_executables + + +def test_run_program_set_with_inputs_invalid(program_set): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + with pytest.raises(ValueError): + _ = device.run(program_set, shots=10, inputs={"foo": "0.1"}) + + +def test_run_program_set_ir_with_inputs_invalid(program_set_ir): + dummy_sim = DummyProgramSetSimulator() + device = LocalSimulator(dummy_sim) + with pytest.raises(ValueError): + _ = device.run(program_set_ir, shots=10, inputs={"foo": "0.2"}) diff --git a/test/unit_tests/braket/program_sets/conftest.py b/test/unit_tests/braket/program_sets/conftest.py new file mode 100644 index 000000000..338e8f61a --- /dev/null +++ b/test/unit_tests/braket/program_sets/conftest.py @@ -0,0 +1,22 @@ +# 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. + +import pytest + +from braket.circuits import Circuit +from braket.parametric import FreeParameter + + +@pytest.fixture +def circuit_rx_parametrized() -> Circuit: + return Circuit().rx(0, FreeParameter("theta")) diff --git a/test/unit_tests/braket/program_sets/program_set_test_utils.py b/test/unit_tests/braket/program_sets/program_set_test_utils.py new file mode 100644 index 000000000..92729e320 --- /dev/null +++ b/test/unit_tests/braket/program_sets/program_set_test_utils.py @@ -0,0 +1,26 @@ +# 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. + +from braket.circuits import Circuit +from braket.circuits.serialization import IRType + + +def get_circuit_source(circuit): + return circuit.to_ir(IRType.OPENQASM).source + + +def ghz(n): + circuit = Circuit().h(0) + for i in range(n - 1): + circuit.cnot(i, i + 1) + return circuit diff --git a/test/unit_tests/braket/program_sets/test_circuit_binding.py b/test/unit_tests/braket/program_sets/test_circuit_binding.py new file mode 100644 index 000000000..7f6c1a8ae --- /dev/null +++ b/test/unit_tests/braket/program_sets/test_circuit_binding.py @@ -0,0 +1,47 @@ +# 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. + +import pytest + +from braket.circuits import Circuit +from braket.circuits.observables import X, Y, Z +from braket.program_sets import CircuitBinding + + +def test_equality(circuit_rx_parametrized): + input_sets = {"theta": [1.23, 3.21]} + observable = X(0) @ Z(1) + 3 * Y(0) + cb = CircuitBinding(circuit_rx_parametrized, input_sets, observable) + assert cb == CircuitBinding( + circuit_rx_parametrized, [{"theta": 1.23}, {"theta": 3.21}], observable + ) + assert cb != CircuitBinding(circuit_rx_parametrized, observables=observable) + assert cb != CircuitBinding(circuit_rx_parametrized, input_sets) + assert cb != circuit_rx_parametrized + + +def test_input_sets_observables_missing(): + with pytest.raises(ValueError): + CircuitBinding(Circuit().h(0)) + + +def test_result_type(circuit_rx_parametrized): + with pytest.raises(ValueError): + CircuitBinding( + Circuit(circuit_rx_parametrized).expectation(X(0)), input_sets={"theta": [1.23, 3.21]} + ) + + +def test_sum_in_observable_list(): + with pytest.raises(TypeError): + CircuitBinding(Circuit().h(0), observables=[X(0) + Y(0)]) diff --git a/test/unit_tests/braket/program_sets/test_parameter_sets.py b/test/unit_tests/braket/program_sets/test_parameter_sets.py new file mode 100644 index 000000000..6663af2b7 --- /dev/null +++ b/test/unit_tests/braket/program_sets/test_parameter_sets.py @@ -0,0 +1,163 @@ +# 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. + +import numpy as np +import pytest + +from braket.program_sets.parameter_sets import ParameterSets, _strict_zip + + +def test_dict(): + parameter_sets = ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}) + assert parameter_sets.as_dict() == {"theta": [1, 2, 3], "phi": [4, 5, 6]} + assert len(parameter_sets) == 3 + + +def test_list(): + parameter_sets = ParameterSets([ + {"theta": 1, "phi": 4}, + {"theta": 2, "phi": 5}, + {"theta": 3, "phi": 6}, + ]) + assert parameter_sets.as_dict() == {"theta": [1, 2, 3], "phi": [4, 5, 6]} + assert len(parameter_sets) == 3 + + +def test_keys_values(): + parameter_sets = ParameterSets(keys=("theta", "phi"), values=np.array([[1, 2, 3], [4, 5, 6]])) + assert parameter_sets.as_dict() == {"theta": [1, 2, 3], "phi": [4, 5, 6]} + assert len(parameter_sets) == 3 + + +def test_kwargs(): + parameter_sets = ParameterSets(theta=[1, 2, 3], phi=[4, 5, 6]) + assert parameter_sets.as_dict() == {"theta": [1, 2, 3], "phi": [4, 5, 6]} + assert len(parameter_sets) == 3 + + +def test_other_parameter_sets(): + parameter_sets = ParameterSets(ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]})) + assert parameter_sets.as_dict() == {"theta": [1, 2, 3], "phi": [4, 5, 6]} + assert len(parameter_sets) == 3 + + +def test_empty(): + parameter_sets = ParameterSets() + assert parameter_sets.as_dict() is None + assert len(parameter_sets) == 0 + + +def test_add(): + parameter_sets = ( + ParameterSets(keys=("theta", "phi"), values=np.array([[1, 2], [8, 9]])) + + [{"theta": 3, "phi": 10}, {"theta": 4, "phi": 11}] + + {"theta": [5, 6, 7], "phi": [12, 13, 14]} + ) + assert parameter_sets.as_dict() == { + "theta": [1, 2, 3, 4, 5, 6, 7], + "phi": [8, 9, 10, 11, 12, 13, 14], + } + assert len(parameter_sets) == 7 + + +def test_equality(): + assert ParameterSets(theta=[1, 2, 3], phi=[4, 5, 6]) == ParameterSets( + keys=("theta", "phi"), values=np.array([[1, 2, 3], [4, 5, 6]]) + ) + assert ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}) == [ + {"theta": 1, "phi": 4}, + {"theta": 2, "phi": 5}, + {"theta": 3, "phi": 6}, + ] + assert ParameterSets(keys=("theta", "phi"), values=np.array([[1, 2, 3], [4, 5, 6]])) != [ + {"theta": 1, "phi": 4}, + {"theta": 2, "phi": 5}, + {"theta": 3, "phi": 7}, + ] + assert ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}) != "foo" + + +def test_parameter_sets_wrong_type(): + with pytest.raises(TypeError): + ParameterSets(1234) + + +def test_inputs_and_keys(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}, keys=["lam"]) + + +def test_inputs_and_values(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}, values=[[7, 8, 9]]) + + +def test_inputs_and_kwargs(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}, lam=[7, 8, 9]) + + +def test_kwargs_and_keys(): + with pytest.raises(ValueError): + ParameterSets(theta=[1, 2, 3], phi=[4, 5, 6], keys=["lam"]) + + +def test_kwargs_and_values(): + with pytest.raises(ValueError): + ParameterSets(theta=[1, 2, 3], phi=[4, 5, 6], values=[[7, 8, 9]]) + + +def test_keys_values_mismatch(): + with pytest.raises(ValueError): + ParameterSets(theta=[1, 2, 3], phi=[4, 5]) + + +def test_keys_no_values(): + with pytest.raises(ValueError): + ParameterSets(keys=("theta", "phi")) + + +def test_values_no_keys(): + with pytest.raises(ValueError): + ParameterSets(values=np.array([[1, 2, 3], [4, 5, 6]])) + + +def test_list_mismatched_missing(): + with pytest.raises(ValueError): + ParameterSets([{"theta": 1, "phi": 4}, {"theta": 2, "phi": 5}, {"theta": 3}]) + + +def test_list_mismatched_extra(): + with pytest.raises(ValueError): + ParameterSets([{"theta": 1}, {"theta": 2}, {"theta": 3, "phi": 6}]) + + +def test_dict_mismatched(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5]}) + + +def test_kwargs_mismatched(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5]}) + + +def test_add_mismatch(): + with pytest.raises(ValueError): + ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}) + [{"theta": 3}, {"theta": 10}] + + +@pytest.mark.parametrize("lists", [[[1, 2, 2], [2, 3, 4], [1, 2]], [[1, 2], [1, 2, 3]]]) +def test_strict_zip_mismatch(lists): + with pytest.raises(ValueError): + _strict_zip(*lists) diff --git a/test/unit_tests/braket/program_sets/test_program_set.py b/test/unit_tests/braket/program_sets/test_program_set.py new file mode 100644 index 000000000..2706007d4 --- /dev/null +++ b/test/unit_tests/braket/program_sets/test_program_set.py @@ -0,0 +1,536 @@ +# 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. + +import numpy as np +import pytest +from program_set_test_utils import get_circuit_source, ghz + +from braket.circuits import Circuit +from braket.circuits.observable import euler_angle_parameter_names +from braket.circuits.observables import X, Y, Z +from braket.ir.openqasm.program_set_v1 import ProgramSet as IrProgramSet +from braket.ir.openqasm.program_v1 import Program +from braket.parametric import FreeParameter +from braket.program_sets.circuit_binding import CircuitBinding +from braket.program_sets.program_set import ProgramSet + + +def test_single_circuit_binding(circuit_rx_parametrized): + binding = CircuitBinding(circuit_rx_parametrized, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + program_set = ProgramSet(binding) + assert len(program_set) == 1 + assert program_set.total_executables == 2 + assert program_set[0] == program_set.entries[0] == binding + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(circuit_rx_parametrized), inputs={"theta": [1.23, 3.21]} + ) + ] + ) + assert program_set == ProgramSet([binding]) + with pytest.raises(ValueError): + program_set.total_shots + + +def test_multiple_programs(circuit_rx_parametrized): + circ1 = Circuit().rx(0, 1.23) + circ2 = Circuit().rx(0, 3.21) + binding = CircuitBinding(circuit_rx_parametrized, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + program_set = ProgramSet([circ1, binding, circ2], 100) + assert len(program_set) == 3 + assert program_set.total_executables == 4 + assert program_set[0] == program_set.entries[0] == circ1 + assert program_set[1] == program_set.entries[1] == binding + assert program_set[2] == program_set.entries[2] == circ2 + inputs = {"theta": [1.23, 3.21]} + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program(source=get_circuit_source(circ1), inputs={}), + Program(source=get_circuit_source(circuit_rx_parametrized), inputs=inputs), + Program(source=get_circuit_source(circ2), inputs={}), + ] + ) + assert program_set.total_shots == 400 + assert program_set == ProgramSet([circ1, binding, circ2], shots_per_executable=100) + assert str(program_set) == ( + f"ProgramSet(programs=[{repr(circ1)}, " + f"CircuitBinding(circuit={repr(circuit_rx_parametrized)}, " + f"input_sets={inputs}, " + f"observables=None), " + f"{repr(circ2)}], " + f"shots_per_executable=100)" + ) + + +def test_add(circuit_rx_parametrized): + circuit_h = ghz(1) + circuit_bell = ghz(2) + ghz3 = ghz(3) + program_set = ( + ProgramSet([circuit_h, circuit_bell], 100) + + [CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [3.21, 2.31]}), ghz3] + + ProgramSet(CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]})) + ) + assert len(program_set) == 5 + assert program_set.total_executables == 7 + assert program_set.total_shots == 700 + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program(source=get_circuit_source(circuit_h), inputs={}), + Program(source=get_circuit_source(circuit_bell), inputs={}), + Program( + source=get_circuit_source(circuit_rx_parametrized), inputs={"theta": [3.21, 2.31]} + ), + Program(source=get_circuit_source(ghz3), inputs={}), + Program( + source=get_circuit_source(circuit_rx_parametrized), inputs={"theta": [1.23, 0.12]} + ), + ] + ) + + +def test_add_other_shots(circuit_rx_parametrized): + circuit_h = ghz(1) + circuit_bell = ghz(2) + ghz3 = ghz(3) + program_set = ( + ProgramSet([circuit_h, circuit_bell]) + + [CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [3.21, 2.31]}), ghz3] + + ProgramSet( + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]}), 100 + ) + ) + assert program_set.total_shots == 700 + + +def test_result_type(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet([ + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [3.21, 2.31]}), + ghz(2).expectation(X(0)), + ]) + + +def test_add_mismatched_shots(): + circuit_h = ghz(1) + circuit_bell = ghz(2) + with pytest.raises(ValueError): + ProgramSet([circuit_h, circuit_bell], 100) + ProgramSet([circuit_h, circuit_bell], 1000) + + +def test_add_wrong_type(): + with pytest.raises(TypeError): + ProgramSet([Circuit().h(0), Circuit().rx(0, 1.23)]) + 3 + + +def test_zip_binding_observables(circuit_rx_parametrized): + obs1 = Y(1) + obs2 = Z(0) @ X(1) + program_set = ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, observables=[obs1, obs2]), + input_sets=[{"theta": 1.23}, {"theta": 3.21}], + shots_per_executable=100, + ) + assert len(program_set) == 2 + assert program_set.total_executables == 2 + assert program_set.total_shots == 200 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles([obs1])), + inputs={ + "theta": [1.23], + euler_1[0]: [0], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles([obs2])), + inputs={ + "theta": [3.21], + euler_0[0]: [0], + euler_0[1]: [0], + euler_0[2]: [0], + euler_1[0]: [np.pi / 2], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + ] + ) + + +def test_zip_binding_observables_sum(circuit_rx_parametrized): + with pytest.raises(TypeError): + ProgramSet.zip(CircuitBinding(circuit_rx_parametrized, observables=Y(1) + Z(0) @ X(1))) + + +def test_zip_binding_observables_and_ps_observables(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, observables=[X(0), Y(0), Z(0)]), + observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)], + ) + + +def test_zip_binding_observables_no_inputs(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, observables=[X(0), Y(0), Z(0)]), + ) + + +def test_zip_binding_observables_input_sets_mismatch(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)]), + input_sets=[{"theta": 1.23}, {"theta": 3.21}], + ) + + +def test_zip_binding_input_sets(circuit_rx_parametrized): + obs1 = Y(1) + obs2 = Z(0) @ X(1) + program_set = ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 3.21]}), + observables=[obs1, obs2], + shots_per_executable=100, + ) + assert len(program_set) == 2 + assert program_set.total_executables == 2 + assert program_set.total_shots == 200 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles([obs1])), + inputs={ + "theta": [1.23], + euler_1[0]: [0], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles([obs2])), + inputs={ + "theta": [3.21], + euler_0[0]: [0], + euler_0[1]: [0], + euler_0[2]: [0], + euler_1[0]: [np.pi / 2], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + ] + ) + + +def test_zip_binding_input_sets_and_ps_input_sets(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]}), + input_sets=[{"theta": 1.23}, {"theta": 3.21}], + ) + + +def test_zip_binding_input_sets_no_observables(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip(CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]})) + + +def test_zip_binding_input_sets_sum_in_observables(circuit_rx_parametrized): + with pytest.raises(TypeError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]}), + observables=[X(0) @ Y(1) + Y(0), Z(0) @ X(1)], + ) + + +def test_zip_binding_input_sets_observables_mismatch(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.zip( + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]}), + observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)], + ) + + +def test_zip_circuits_input_sets_observables(): + circ1 = Circuit().rx(0, FreeParameter("theta")) + circ2 = Circuit().ry(0, FreeParameter("phi")) + input_sets = [{"theta": 1.23}, {"phi": 3.21}] + obs1 = Y(1) + obs2 = Z(0) @ X(1) + program_set = ProgramSet.zip( + [circ1, circ2], + input_sets=input_sets, + observables=[obs1, obs2], + shots_per_executable=100, + ) + assert len(program_set) == 2 + assert program_set.total_executables == 2 + assert program_set.total_shots == 200 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(circ1.with_euler_angles([obs1])), + inputs={ + "theta": [1.23], + euler_1[0]: [0], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + Program( + source=get_circuit_source(circ2.with_euler_angles([obs2])), + inputs={ + "phi": [3.21], + euler_0[0]: [0], + euler_0[1]: [0], + euler_0[2]: [0], + euler_1[0]: [np.pi / 2], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + ] + ) + + +def test_zip_circuits_input_sets_observables_mismatch(): + with pytest.raises(ValueError): + ProgramSet.zip( + [ghz(1), ghz(2)], + input_sets=[{"theta": 1.23}, {"theta": 3.21}], + observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)], + ) + with pytest.raises(ValueError): + ProgramSet.zip( + [ghz(1), ghz(2), ghz(3)], + input_sets=[{"theta": 1.23}, {"theta": 3.21}], + observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)], + ) + + +def test_zip_circuits_observables(): + circ1 = ghz(2) + circ2 = ghz(3) + obs1 = Y(1) + obs2 = Z(0) @ X(1) + program_set = ProgramSet.zip( + [circ1, circ2], + observables=[obs1, obs2], + shots_per_executable=100, + ) + assert len(program_set) == 2 + assert program_set.total_executables == 2 + assert program_set.total_shots == 200 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(circ1.with_euler_angles([obs1])), + inputs={euler_1[0]: [0], euler_1[1]: [np.pi / 2], euler_1[2]: [np.pi / 2]}, + ), + Program( + source=get_circuit_source(circ2.with_euler_angles([obs2])), + inputs={ + euler_0[0]: [0], + euler_0[1]: [0], + euler_0[2]: [0], + euler_1[0]: [np.pi / 2], + euler_1[1]: [np.pi / 2], + euler_1[2]: [np.pi / 2], + }, + ), + ] + ) + + +def test_zip_circuits_observables_mismatch(): + with pytest.raises(ValueError): + ProgramSet.zip([ghz(1), ghz(2)], observables=[X(0) @ Y(1), Y(0), Z(0) @ X(1)]) + + +def test_zip_circuits_input_sets(): + circ1 = Circuit().rx(0, FreeParameter("theta")) + circ2 = Circuit().ry(0, FreeParameter("phi")) + input_sets = [{"theta": 1.23}, {"phi": 3.21}] + program_set = ProgramSet.zip([circ1, circ2], input_sets=input_sets, shots_per_executable=100) + assert len(program_set) == 2 + assert program_set.total_executables == 2 + assert program_set.total_shots == 200 + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program(source=get_circuit_source(circ1), inputs={"theta": [1.23]}), + Program(source=get_circuit_source(circ2), inputs={"phi": [3.21]}), + ] + ) + + +def test_zip_circuits_input_sets_mismatch(): + with pytest.raises(ValueError): + ProgramSet.zip([ghz(1), ghz(2), ghz(3)], input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + + +def test_zip_circuits_only(): + with pytest.raises(ValueError): + ProgramSet.zip([ghz(1), ghz(2), ghz(3)]) + + +def test_product_observables(circuit_rx_parametrized): + ghz2 = ghz(2) + ghz3 = ghz(3) + observables = [Y(1), Z(0) @ X(1)] + program_set = ProgramSet.product( + [ghz2, CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 3.21]}), ghz3], + observables=observables, + shots_per_executable=100, + ) + assert len(program_set) == 3 + assert program_set.total_executables == 8 + assert program_set.total_shots == 800 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(ghz2.with_euler_angles(observables)), + inputs={ + euler_0[0]: [0, 0], + euler_0[1]: [0, 0], + euler_0[2]: [0, 0], + euler_1[0]: [0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2], + }, + ), + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles(observables)), + inputs={ + "theta": [1.23, 1.23, 3.21, 3.21], + euler_0[0]: [0, 0, 0, 0], + euler_0[1]: [0, 0, 0, 0], + euler_0[2]: [0, 0, 0, 0], + euler_1[0]: [0, np.pi / 2, 0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + }, + ), + Program( + source=get_circuit_source(ghz3.with_euler_angles(observables)), + inputs={ + euler_0[0]: [0, 0], + euler_0[1]: [0, 0], + euler_0[2]: [0, 0], + euler_1[0]: [0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2], + }, + ), + ] + ) + + +def test_product_sum(circuit_rx_parametrized): + ghz2 = ghz(2) + ghz3 = ghz(3) + observables = 3 * Y(1) - 2 * Z(0) @ X(1) + program_set = ProgramSet.product( + [ghz2, CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 3.21]}), ghz3], + observables=observables, + shots_per_executable=100, + ) + assert len(program_set) == 3 + assert program_set.total_executables == 8 + assert program_set.total_shots == 800 + euler_0 = euler_angle_parameter_names(0) + euler_1 = euler_angle_parameter_names(1) + assert program_set.to_ir() == IrProgramSet( + programs=[ + Program( + source=get_circuit_source(ghz2.with_euler_angles(observables)), + inputs={ + euler_0[0]: [0, 0], + euler_0[1]: [0, 0], + euler_0[2]: [0, 0], + euler_1[0]: [0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2], + }, + ), + Program( + source=get_circuit_source(circuit_rx_parametrized.with_euler_angles(observables)), + inputs={ + "theta": [1.23, 1.23, 3.21, 3.21], + euler_0[0]: [0, 0, 0, 0], + euler_0[1]: [0, 0, 0, 0], + euler_0[2]: [0, 0, 0, 0], + euler_1[0]: [0, np.pi / 2, 0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + }, + ), + Program( + source=get_circuit_source(ghz3.with_euler_angles(observables)), + inputs={ + euler_0[0]: [0, 0], + euler_0[1]: [0, 0], + euler_0[2]: [0, 0], + euler_1[0]: [0, np.pi / 2], + euler_1[1]: [np.pi / 2, np.pi / 2], + euler_1[2]: [np.pi / 2, np.pi / 2], + }, + ), + ] + ) + + +def test_product_no_observables(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.product( + [ + ghz(1), + CircuitBinding(circuit_rx_parametrized, input_sets={"theta": [1.23, 0.12]}), + ghz(2), + ], + [], + ) + + +def test_product_binding_observables(circuit_rx_parametrized): + with pytest.raises(ValueError): + ProgramSet.product( + [ + ghz(1), + CircuitBinding(circuit_rx_parametrized, observables=X(0) @ Y(1) + Y(0) @ Z(1)), + ghz(2), + ], + [X(0) @ Y(1), Y(0) @ Z(1)], + ) + + +def test_inequality(circuit_rx_parametrized): + binding = CircuitBinding(circuit_rx_parametrized, input_sets=[{"theta": 1.23}, {"theta": 3.21}]) + program_set = ProgramSet([binding, binding]) + assert program_set != ProgramSet([binding, circuit_rx_parametrized]) + assert program_set != circuit_rx_parametrized diff --git a/test/unit_tests/braket/tasks/test_program_set_quantum_task_result.py b/test/unit_tests/braket/tasks/test_program_set_quantum_task_result.py new file mode 100644 index 000000000..d50b79bcb --- /dev/null +++ b/test/unit_tests/braket/tasks/test_program_set_quantum_task_result.py @@ -0,0 +1,425 @@ +# 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. + +import json +from unittest.mock import Mock, patch + +import numpy as np +import pytest + +from braket.circuits import Circuit +from braket.circuits.observables import X, Y, Z +from braket.parametric import FreeParameter +from braket.program_sets import CircuitBinding, ParameterSets, ProgramSet +from braket.schema_common import BraketSchemaBase +from braket.tasks import ProgramSetQuantumTaskResult +from braket.tasks.program_set_quantum_task_result import CompositeEntry, MeasuredEntry + + +@pytest.fixture +def execution_measurements(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_result", + "version": "1", + }, + "measurements": [ + [0, 0], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + [0, 0], + [0, 0], + [1, 1], + [1, 1], + [1, 1], + [0, 0], + [1, 1], + [0, 0], + ], + "measuredQubits": [0, 1], + "inputsIndex": 0, + } + + +@pytest.fixture +def execution_measurement_probabilities(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_result", + "version": "1", + }, + "measurementProbabilities": {"00": 0.7, "11": 0.3}, + "measuredQubits": [0, 1], + "inputsIndex": 0, + } + + +@pytest.fixture +def execution_results_missing(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_result", + "version": "1", + }, + "measuredQubits": [0, 1], + "inputsIndex": 0, + } + + +@pytest.fixture +def execution_failure(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_executable_failure", + "version": "1", + }, + "inputsIndex": 0, + "failureMetadata": { + "failureReason": "QPU was sick, should be good again after getting some sleep", + "retryable": True, + "category": "DEVICE", + }, + } + + +@pytest.fixture +def program(): + return { + "braketSchemaHeader": {"name": "braket.ir.openqasm.program", "version": "1"}, + "source": "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\nh q[0];\ncnot q[0], q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];", # noqa + "inputs": {"theta": [0.12, 2.1]}, + } + + +@pytest.fixture +def program_observables(): + return { + "braketSchemaHeader": {"name": "braket.ir.openqasm.program", "version": "1"}, + "source": "OPENQASM 3.0;\nbit[2] b;\nqubit[2] q;\nh q[0];\ncnot q[0], q[1];\nb[0] = measure q[0];\nb[1] = measure q[1];", # noqa + "inputs": { + "theta": [0.12, 0.12, 0.12, 2.1, 2.1, 2.1], + "_OBSERVABLE_THETA_0": [0, np.pi / 2, 0, 0, np.pi / 2, 0], + "_OBSERVABLE_PHI_0": [0, np.pi / 2, np.pi / 2, 0, np.pi / 2, np.pi / 2], + "_OBSERVABLE_OMEGA_0": [0, np.pi / 2, np.pi / 2, 0, np.pi / 2, np.pi / 2], + }, + } + + +@pytest.fixture +def program_result_local(execution_measurement_probabilities, execution_failure, program): + return { + "braketSchemaHeader": {"name": "braket.task_result.program_result", "version": "1"}, + "executableResults": [execution_measurement_probabilities, execution_failure], + "source": program, + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 50, + } + }, + } + + +@pytest.fixture +def program_result_s3(): + return { + "braketSchemaHeader": {"name": "braket.task_result.program_result", "version": "1"}, + "executableResults": ["executables/0.json"], + "source": "program.json", + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 47, + } + }, + } + + +@pytest.fixture +def program_result_observables(execution_measurement_probabilities, program_observables): + executable_results = [dict(execution_measurement_probabilities) for _ in range(6)] + for i, executable_result in enumerate(executable_results): + executable_result["inputsIndex"] = i + result = { + "braketSchemaHeader": {"name": "braket.task_result.program_result", "version": "1"}, + "executableResults": executable_results, + "source": program_observables, + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 100, + } + }, + } + return result + + +@pytest.fixture +def metadata(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_metadata", + "version": "1", + }, + "id": "arn:aws:braket:us-west-2:667256736152:quantum-task/bfebc86f-e4ed-4d6f-8131-addd1a49d6dc", # noqa + "deviceId": "arn:aws:braket:::device/quantum-simulator/amazon/sv1", + "requestedShots": 120, + "successfulShots": 100, + "programMetadata": [{"executables": [{}, {}, {}]}], + "deviceParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.simulators.gate_model_simulator_device_parameters", + "version": "1", + }, + "paradigmParameters": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_parameters", + "version": "1", + }, + "qubitCount": 5, + "disableQubitRewiring": False, + }, + }, + "createdAt": "2024-10-15T19:06:58.986Z", + "endedAt": "2024-10-15T19:07:00.382Z", + "status": "COMPLETED", + "totalFailedExecutables": 1, + } + + +@pytest.fixture +def result_local(metadata, program_result_local): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": [program_result_local], + "taskMetadata": metadata, + } + + +@pytest.fixture +def result_s3(): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": ["programs/0/results.json"], + "s3Location": ["amazon-braket-foo", "tasks/bar"], + "taskMetadata": "metadata.json", + } + + +@pytest.fixture +def result_observables(metadata, program_result_observables): + return { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": [program_result_observables], + "taskMetadata": metadata, + } + + +def test_local(result_local, metadata, execution_failure): + result = ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(json.dumps(result_local)) + ) + assert len(result.programs) == 1 + assert result.num_executables == 3 # From metadata + assert len(result) == 1 + + entry = result[0] + assert isinstance(entry, CompositeEntry) + assert len(entry) == 2 + assert entry.inputs == ParameterSets({"theta": [0.12, 2.1]}) + with pytest.raises(ValueError): + entry.expectation(0) + assert entry.additional_metadata.simulatorMetadata.executionDuration == 50 + + success = entry[0] + assert isinstance(success, MeasuredEntry) + assert success.probabilities == {"00": 0.7, "11": 0.3} + assert success.counts["00"] == 28 + assert success.counts["11"] == 12 + assert len(success.counts) == 2 + assert len(success.measurements) == 40 + with pytest.warns(UserWarning): + assert success.expectation is None + assert entry[1] == BraketSchemaBase.parse_raw_schema(json.dumps(execution_failure)) + assert result.task_metadata == BraketSchemaBase.parse_raw_schema(json.dumps(metadata)) + + +@patch("braket.tasks.program_set_quantum_task_result.boto3.client") +def test_s3( + mock_boto3_client, result_s3, program_result_s3, program, execution_measurements, metadata +): + mock_read_object = Mock() + mock_read_object.decode.side_effect = [ + json.dumps(f) for f in [metadata, program_result_s3, program, execution_measurements] + ] + mock_body_object = Mock() + mock_body_object.read.return_value = mock_read_object + mock_client = Mock() + mock_client.get_object.return_value = {"Body": mock_body_object} + mock_boto3_client.return_value = mock_client + result = ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(json.dumps(result_s3)) + ) + bucket = "amazon-braket-foo" + assert mock_client.get_object.call_args_list == [ + (dict(Bucket=bucket, Key="tasks/bar/metadata.json"),), + (dict(Bucket=bucket, Key="tasks/bar/programs/0/results.json"),), + (dict(Bucket=bucket, Key="tasks/bar/programs/0/program.json"),), + (dict(Bucket=bucket, Key="tasks/bar/programs/0/executables/0.json"),), + ] + + assert len(result.programs) == 1 + assert result.num_executables == 3 # From metadata + assert len(result) == 1 + + entry = result[0] + assert entry.inputs == ParameterSets({"theta": [0.12, 2.1]}) + with pytest.raises(ValueError): + entry.expectation(0) + assert entry.additional_metadata.simulatorMetadata.executionDuration == 47 + + assert isinstance(entry, CompositeEntry) + success = entry[0] + assert isinstance(success, MeasuredEntry) + assert len(success.measurements) == 20 + assert success.counts["00"] == 11 + assert success.counts["11"] == 9 + assert len(success.counts) == 2 + assert np.isclose(success.probabilities["00"], 0.55) + assert np.isclose(success.probabilities["11"], 0.45) + assert len(success.probabilities) == 2 + with pytest.warns(UserWarning): + assert success.expectation is None + + +def test_observables(result_observables, metadata): + result = ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(json.dumps(result_observables)), + ProgramSet( + CircuitBinding( + Circuit().rx(0, FreeParameter("theta")), + input_sets={"theta": [0.12, 2.1]}, + observables=10 * Z(0) + X(0) - 0.01 * Y(0) @ X(1), + ) + ), + ) + assert len(result.programs) == 1 + assert result.num_executables == 3 # From metadata + assert len(result) == 1 + + composite = result[0] + assert isinstance(composite, CompositeEntry) + assert composite.inputs == ParameterSets({"theta": [0.12, 2.1]}) + with pytest.raises(ValueError): + composite.expectation() + with pytest.raises(ValueError): + composite.expectation(2) + assert len(composite) == 6 + assert composite.additional_metadata.simulatorMetadata.executionDuration == 100 + + for measured in composite: + assert isinstance(measured, MeasuredEntry) + assert measured.probabilities == {"00": 0.7, "11": 0.3} + assert measured.counts["00"] == 28 + assert measured.counts["11"] == 12 + assert len(measured.counts) == 2 + assert len(measured.measurements) == 40 + assert composite[3].expectation == 4 + assert composite[4].expectation == 0.4 + assert composite[5].expectation == -0.01 + + +def test_observables_no_inputs(result_observables, metadata): + del result_observables["programResults"][0]["source"]["inputs"]["theta"] + h = 10000 * Z(0) + 1000 * X(0) - 100 * Z(0) + 10 * Z(1) + X(1) - 0.1 * Y(1) + result = ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(json.dumps(result_observables)), + ProgramSet(CircuitBinding(Circuit().h(0).cnot(0, 1), observables=h)), + ) + assert len(result.programs) == 1 + assert result.num_executables == 3 # From metadata + assert len(result) == 1 + + composite = result[0] + assert isinstance(composite, CompositeEntry) + assert composite.inputs == ParameterSets({}) + assert np.isclose(composite.expectation(), 4364.36) + with pytest.raises(ValueError): + composite.expectation(1) + assert len(composite) == 6 + assert composite.additional_metadata.simulatorMetadata.executionDuration == 100 + + for measured in composite: + assert isinstance(measured, MeasuredEntry) + assert measured.probabilities == {"00": 0.7, "11": 0.3} + assert measured.counts["00"] == 28 + assert measured.counts["11"] == 12 + assert len(measured.counts) == 2 + assert len(measured.measurements) == 40 + + +def test_results_missing(metadata, execution_results_missing, program): + result = { + "braketSchemaHeader": { + "name": "braket.task_result.program_set_task_result", + "version": "1", + }, + "programResults": [ + { + "braketSchemaHeader": {"name": "braket.task_result.program_result", "version": "1"}, + "executableResults": [execution_results_missing], + "source": program, + "additionalMetadata": { + "simulatorMetadata": { + "braketSchemaHeader": { + "name": "braket.task_result.simulator_metadata", + "version": "1", + }, + "executionDuration": 50, + } + }, + } + ], + "taskMetadata": metadata, + } + with pytest.raises(ValueError): + ProgramSetQuantumTaskResult.from_object( + BraketSchemaBase.parse_raw_schema(json.dumps(result)) + ) From 1b3206220b37d267978902e0e8b5228582f6b8a6 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Aug 2025 17:41:15 +0000 Subject: [PATCH 309/347] prepare release v1.97.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee9ab86d4..a09683a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.97.0 (2025-08-13) + +### Features + + * Program sets + ## v1.96.1 (2025-08-05) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5c8ef83d0..437ea1415 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.96.2.dev0" +__version__ = "1.97.0" From 31f56261c11796a98ebadf158c5bd56d310d4198 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Aug 2025 17:41:15 +0000 Subject: [PATCH 310/347] update development version to v1.97.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 437ea1415..c3d90418a 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.97.0" +__version__ = "1.97.1.dev0" From 9c37c5c8b57ff30fc58cf901c8db60db34b5afeb Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:23:19 -0400 Subject: [PATCH 311/347] feat: add cudaq container image uri (#1107) --- src/braket/jobs/image_uri_config/cudaq.json | 11 +++++++++++ src/braket/jobs/image_uris.py | 1 + test/unit_tests/braket/jobs/test_image_uris.py | 5 +++++ 3 files changed, 17 insertions(+) create mode 100644 src/braket/jobs/image_uri_config/cudaq.json diff --git a/src/braket/jobs/image_uri_config/cudaq.json b/src/braket/jobs/image_uri_config/cudaq.json new file mode 100644 index 000000000..6caa4cfb2 --- /dev/null +++ b/src/braket/jobs/image_uri_config/cudaq.json @@ -0,0 +1,11 @@ +{ + "registry": "292282985366", + "repository": "amazon-braket-cudaq-jobs", + "supported_regions": [ + "us-east-1", + "us-west-1", + "us-west-2", + "eu-west-2", + "eu-north-1" + ] +} \ No newline at end of file diff --git a/src/braket/jobs/image_uris.py b/src/braket/jobs/image_uris.py index 213d06553..379cb9580 100644 --- a/src/braket/jobs/image_uris.py +++ b/src/braket/jobs/image_uris.py @@ -23,6 +23,7 @@ class Framework(str, Enum): BASE = "BASE" PL_TENSORFLOW = "PL_TENSORFLOW" PL_PYTORCH = "PL_PYTORCH" + CUDAQ = "CUDAQ" def built_in_images(region: str) -> set[str]: diff --git a/test/unit_tests/braket/jobs/test_image_uris.py b/test/unit_tests/braket/jobs/test_image_uris.py index 2e6ae3963..33abbbaa0 100644 --- a/test/unit_tests/braket/jobs/test_image_uris.py +++ b/test/unit_tests/braket/jobs/test_image_uris.py @@ -34,6 +34,11 @@ Framework.PL_PYTORCH, "292282985366.dkr.ecr.us-west-2.amazonaws.com/amazon-braket-pytorch-jobs:latest", ), + ( + "us-west-1", + Framework.CUDAQ, + "292282985366.dkr.ecr.us-west-1.amazonaws.com/amazon-braket-cudaq-jobs:latest", + ), ], ) def test_retrieve_image_default_version(region, framework, expected_uri): From 5b26bb0d56d6d7208a2ab9f1d26941be1f0c725e Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 20 Aug 2025 01:22:50 -0700 Subject: [PATCH 312/347] doc: Add Python 3.12, 3.13 to supported versions (#1108) --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index d01923f11..969d9632b 100644 --- a/setup.py +++ b/setup.py @@ -85,5 +85,7 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ], ) From c219bad1e72e193892d4ce90bfd74fcc3867f948 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 20 Aug 2025 13:24:34 +0000 Subject: [PATCH 313/347] prepare release v1.98.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a09683a5b..a4c49e90a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.98.0 (2025-08-20) + +### Features + + * add cudaq container image uri + +### Documentation Changes + + * Add Python 3.12, 3.13 to supported versions + ## v1.97.0 (2025-08-13) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c3d90418a..0f06136f4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.97.1.dev0" +__version__ = "1.98.0" From 5a7b2b1957469624a63c4f72df06bf03e367d420 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 20 Aug 2025 13:24:34 +0000 Subject: [PATCH 314/347] update development version to v1.98.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0f06136f4..1e3ccb65c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.98.0" +__version__ = "1.98.1.dev0" From de43ab35349dfe238fbd320fd27105d280f25d5b Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+AbeCoull@users.noreply.github.com> Date: Wed, 20 Aug 2025 19:02:01 -0400 Subject: [PATCH 315/347] deprecation: Drop Python 3.9, support 3.12, 3.13 (#1077) Co-authored-by: Cody Wang --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 5 +- .github/workflows/python-package.yml | 6 +- .readthedocs.yml | 2 +- README.md | 4 +- pyproject.toml | 3 +- setup.py | 3 +- .../ahs/analog_hamiltonian_simulation.py | 4 +- src/braket/ahs/canvas.py | 2 +- src/braket/ahs/driving_field.py | 2 +- src/braket/ahs/field.py | 11 ++-- src/braket/ahs/hamiltonian.py | 4 +- src/braket/ahs/local_detuning.py | 2 +- src/braket/ahs/pattern.py | 3 +- src/braket/aws/aws_device.py | 52 ++++++++-------- src/braket/aws/aws_quantum_task.py | 57 ++++++++--------- src/braket/aws/aws_quantum_task_batch.py | 61 +++++++++---------- src/braket/aws/aws_session.py | 34 +++++------ src/braket/aws/queue_information.py | 9 ++- src/braket/circuits/angled_gate.py | 11 ++-- src/braket/circuits/basis_state.py | 5 +- src/braket/circuits/braket_program_context.py | 9 +-- src/braket/circuits/circuit.py | 8 +-- src/braket/circuits/circuit_helpers.py | 2 +- .../circuit_instruction_criteria.py | 3 +- .../noise_model/criteria_input_parsing.py | 9 ++- .../circuits/noise_model/gate_criteria.py | 8 +-- .../circuits/noise_model/measure_criteria.py | 6 +- .../noise_model/observable_criteria.py | 8 +-- .../qubit_initialization_criteria.py | 6 +- .../noise_model/unitary_gate_criteria.py | 6 +- src/braket/circuits/noises.py | 38 ++++++------ src/braket/circuits/observables.py | 4 +- src/braket/circuits/result_type.py | 2 +- .../ascii_circuit_diagram.py | 2 +- .../text_circuit_diagram_utils.py | 2 +- .../unicode_circuit_diagram.py | 2 +- src/braket/devices/device.py | 12 ++-- src/braket/devices/local_simulator.py | 32 +++++----- .../iqm/classical_control.py | 6 +- src/braket/jobs/hybrid_job.py | 4 +- src/braket/jobs/logs.py | 4 +- .../jobs/metrics_data/cwl_metrics_fetcher.py | 3 +- .../parametric/free_parameter_expression.py | 2 +- src/braket/program_sets/circuit_binding.py | 9 ++- src/braket/program_sets/parameter_sets.py | 2 +- src/braket/pulse/ast/approximation_parser.py | 12 ++-- src/braket/pulse/ast/free_parameters.py | 11 ++-- src/braket/pulse/ast/qasm_transformer.py | 4 +- src/braket/registers/qubit_set.py | 4 +- ...iltonian_simulation_quantum_task_result.py | 3 +- .../tasks/program_set_quantum_task_result.py | 3 +- src/braket/tasks/quantum_task.py | 30 ++++----- src/braket/tasks/quantum_task_batch.py | 5 +- src/braket/timings/time_series.py | 6 +- 55 files changed, 262 insertions(+), 287 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 897d3622c..6deff2d3f 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: - python-version: '3.9' + python-version: '3.x' - name: Install dependencies run: | pip install tox diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 2167cd9bf..8733284c2 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -12,11 +12,10 @@ permissions: jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.13"] dependent: - amazon-braket-pennylane-plugin-python diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2131630bf..d72d70269 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -22,7 +22,11 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12", "3.13"] + exclude: + # TODO: Fix test_aws_session.py::test_upload_local_data_absolute + - os: windows-latest + python-version: 3.12 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.readthedocs.yml b/.readthedocs.yml index b6ca23199..42f594159 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -17,7 +17,7 @@ formats: build: os: ubuntu-22.04 tools: - python: "3.9" + python: "3.10" # Optionally set the version of Python and requirements required to build your docs python: diff --git a/README.md b/README.md index 3c3e85d51..620663c15 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ The Amazon Braket Python SDK is an open source library that provides a framework ## Prerequisites Before you begin working with the Amazon Braket SDK, make sure that you've installed or configured the following prerequisites. -### Python 3.9 or greater -Download and install Python 3.9 or greater from [Python.org](https://www.python.org/downloads/). +### Python 3.10 or greater +Download and install Python 3.10 or greater from [Python.org](https://www.python.org/downloads/). ### Git Install Git from https://git-scm.com/downloads. Installation instructions are provided on the download page. diff --git a/pyproject.toml b/pyproject.toml index fbd64cfe6..3d937ada7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ norecursedirs = [ ] [tool.ruff] -target-version = "py39" +target-version = "py310" line-length = 100 format.preview = true format.docstring-code-line-length = 100 @@ -61,7 +61,6 @@ lint.ignore = [ "N803", # Allow uppercase "N806", # Vars will break this rule based on technical names. "N815", # Allowing reviwers to do a per case basis - "PLC0207", # Accessing only the first or last element of `str.split()` without setting `maxsplit=1` "PLC2701", # Allow private function access in code "PLR0913", # Too many variables in function "PLR0914", # Too many local variables diff --git a/setup.py b/setup.py index 969d9632b..b3927de6c 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ name="amazon-braket-sdk", version=version, license="Apache License 2.0", - python_requires=">= 3.9", + python_requires=">= 3.10", packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ @@ -82,7 +82,6 @@ "Natural Language :: English", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index bd6ee4863..0428abe20 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -67,7 +67,9 @@ def from_ir(source: ir.Program) -> AnalogHamiltonianSimulation: AnalogHamiltonianSimulation: The Analog Hamiltonian Simulation. """ atom_arrangement = AtomArrangement() - for site, fill in zip(source.setup.ahs_register.sites, source.setup.ahs_register.filling): + for site, fill in zip( + source.setup.ahs_register.sites, source.setup.ahs_register.filling, strict=False + ): atom_arrangement.add( coordinate=site, site_type=SiteType.FILLED if fill == 1 else SiteType.VACANT ) diff --git a/src/braket/ahs/canvas.py b/src/braket/ahs/canvas.py index 90950d398..a8811eab0 100644 --- a/src/braket/ahs/canvas.py +++ b/src/braket/ahs/canvas.py @@ -43,7 +43,7 @@ def _validate_boundary_points(self, boundary_points: list[tuple[Number, Number]] raise ValueError("Canvas must have at least 3 boundary points") for i, point in enumerate(boundary_points): - if not isinstance(point, (tuple, list)) or len(point) != 2: + if not isinstance(point, tuple | list) or len(point) != 2: raise TypeError(f"Boundary point {i} must be a tuple/list of length 2") for j, coord in enumerate(point): diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index 11d30e34a..a07b5d3d7 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -173,7 +173,7 @@ def from_lists( phase = TimeSeries() for t, amplitude_value, detuning_value, phase_value in zip( - times, amplitudes, detunings, phases + times, amplitudes, detunings, phases, strict=False ): amplitude.put(t, amplitude_value) detuning.put(t, detuning_value) diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index f9c1af16c..8fa1e87c7 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -14,14 +14,13 @@ from __future__ import annotations from decimal import Decimal -from typing import Optional from braket.ahs.pattern import Pattern from braket.timings.time_series import TimeSeries class Field: - def __init__(self, time_series: TimeSeries, pattern: Optional[Pattern] = None) -> None: + def __init__(self, time_series: TimeSeries, pattern: Pattern | None = None) -> None: """A space and time dependent parameter of a program. Args: @@ -37,15 +36,15 @@ def time_series(self) -> TimeSeries: return self._time_series @property - def pattern(self) -> Optional[Pattern]: + def pattern(self) -> Pattern | None: """Optional[Pattern]: The local pattern of real numbers.""" return self._pattern def discretize( self, - time_resolution: Optional[Decimal] = None, - value_resolution: Optional[Decimal] = None, - pattern_resolution: Optional[Decimal] = None, + time_resolution: Decimal | None = None, + value_resolution: Decimal | None = None, + pattern_resolution: Decimal | None = None, ) -> Field: """Creates a discretized version of the field, where time, value and pattern are rounded to the diff --git a/src/braket/ahs/hamiltonian.py b/src/braket/ahs/hamiltonian.py index 548befc58..6e3ef59d5 100644 --- a/src/braket/ahs/hamiltonian.py +++ b/src/braket/ahs/hamiltonian.py @@ -13,13 +13,11 @@ from __future__ import annotations -from typing import Optional - from braket.ahs.discretization_types import DiscretizationProperties class Hamiltonian: - def __init__(self, terms: Optional[list[Hamiltonian]] = None): + def __init__(self, terms: list[Hamiltonian] | None = None): r"""A Hamiltonian representing a system to be simulated. A Hamiltonian :math:`H` may be expressed as a sum of multiple terms diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 43b7abaad..80730b4c5 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -79,7 +79,7 @@ def from_lists(times: list[float], values: list[float], pattern: list[float]) -> raise ValueError("The length of the times and values lists must be equal.") magnitude = TimeSeries() - for t, v in zip(times, values): + for t, v in zip(times, values, strict=False): magnitude.put(t, v) return LocalDetuning(Field(magnitude, Pattern(pattern))) diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py index 92637fe0f..870bf1dd5 100644 --- a/src/braket/ahs/pattern.py +++ b/src/braket/ahs/pattern.py @@ -15,7 +15,6 @@ from decimal import Decimal from numbers import Number -from typing import Optional class Pattern: @@ -35,7 +34,7 @@ def series(self) -> list[Number]: """ return self._series - def discretize(self, resolution: Optional[Decimal]) -> Pattern: + def discretize(self, resolution: Decimal | None) -> Pattern: """Creates a discretized version of the pattern, where each value is rounded to the closest multiple of the resolution. diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 126b4b492..329af760d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -20,7 +20,7 @@ import warnings from datetime import datetime, timezone from enum import Enum -from typing import Any, ClassVar, Optional +from typing import Any, ClassVar import pydantic from botocore.errorfactory import ClientError @@ -71,7 +71,7 @@ class AwsDevice(Device): _GET_DEVICES_ORDER_BY_KEYS = frozenset({"arn", "name", "type", "provider_name", "status"}) - _RIGETTI_GATES_TO_BRAKET: ClassVar[Optional[dict[str, str]]] = { + _RIGETTI_GATES_TO_BRAKET: ClassVar[dict[str, str] | None] = { # Rx_12 does not exist in the Braket SDK, it is a gate between |1> and |2>. "Rx_12": None, "Cz": "CZ", @@ -83,8 +83,8 @@ class AwsDevice(Device): def __init__( self, arn: str, - aws_session: Optional[AwsSession] = None, - noise_model: Optional[NoiseModel] = None, + aws_session: AwsSession | None = None, + noise_model: NoiseModel | None = None, ): """Initializes an `AwsDevice`. @@ -122,13 +122,13 @@ def __init__( def run( self, task_specification: TaskSpecification, - s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, - shots: Optional[int] = None, + s3_destination_folder: AwsSession.S3DestinationFolder | None = None, + shots: int | None = None, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, - poll_interval_seconds: Optional[float] = None, - inputs: Optional[dict[str, float]] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, - reservation_arn: Optional[str] = None, + poll_interval_seconds: float | None = None, + inputs: dict[str, float] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, + reservation_arn: str | None = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, ) -> AwsQuantumTask: @@ -223,15 +223,15 @@ def run( def run_batch( self, task_specifications: TaskSpecification | list[TaskSpecification], - s3_destination_folder: Optional[AwsSession.S3DestinationFolder] = None, - shots: Optional[int] = None, - max_parallel: Optional[int] = None, + s3_destination_folder: AwsSession.S3DestinationFolder | None = None, + shots: int | None = None, + max_parallel: int | None = None, max_connections: int = AwsQuantumTaskBatch.MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, - reservation_arn: Optional[str] = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, + reservation_arn: str | None = None, *aws_quantum_task_args, **aws_quantum_task_kwargs, ) -> AwsQuantumTaskBatch: @@ -401,7 +401,7 @@ def arn(self) -> str: return self._arn @property - def gate_calibrations(self) -> Optional[GateCalibrations]: + def gate_calibrations(self) -> GateCalibrations | None: """Calibration data for a QPU. Calibration data is shown for gates on particular gubits. If a QPU does not expose these calibrations, None is returned. @@ -530,8 +530,8 @@ def _construct_topology_graph(self) -> DiGraph: return from_edgelist(edges, create_using=DiGraph()) return None - def _default_shots(self, task_specification: Optional[TaskSpecification] = None) -> int: - if isinstance(task_specification, (ProgramSet, OpenQASMProgramSet)): + def _default_shots(self, task_specification: TaskSpecification | None = None) -> int: + if isinstance(task_specification, ProgramSet | OpenQASMProgramSet): return AwsDevice.DEFAULT_SHOTS_PROGRAM_SET return ( AwsDevice.DEFAULT_SHOTS_QPU @@ -569,13 +569,13 @@ def ports(self) -> dict[str, Port]: @staticmethod def get_devices( - arns: Optional[list[str]] = None, - names: Optional[list[str]] = None, - types: Optional[list[AwsDeviceType]] = None, - statuses: Optional[list[str]] = None, - provider_names: Optional[list[str]] = None, + arns: list[str] | None = None, + names: list[str] | None = None, + types: list[AwsDeviceType] | None = None, + statuses: list[str] | None = None, + provider_names: list[str] | None = None, order_by: str = "name", - aws_session: Optional[AwsSession] = None, + aws_session: AwsSession | None = None, ) -> list[AwsDevice]: """Get devices based on filters and desired ordering. The result is the AND of all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. @@ -749,7 +749,7 @@ def queue_depth(self) -> QueueDepthInfo: return QueueDepthInfo(**queue_info) - def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: + def refresh_gate_calibrations(self) -> GateCalibrations | None: """Refreshes the gate calibration data upon request. If the device does not have calibration data, None is returned. diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 456bdcad6..bdf605061 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -19,7 +19,7 @@ import warnings from functools import singledispatch from logging import Logger, getLogger -from typing import Any, ClassVar, Optional, Union +from typing import Any, ClassVar import boto3 from botocore.exceptions import ClientError @@ -100,13 +100,13 @@ def create( task_specification: TaskSpecification, s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, - device_parameters: Optional[dict[str, Any]] = None, + device_parameters: dict[str, Any] | None = None, disable_qubit_rewiring: bool = False, - tags: Optional[dict[str, str]] = None, - inputs: Optional[dict[str, float]] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + tags: dict[str, str] | None = None, + inputs: dict[str, float] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, quiet: bool = False, - reservation_arn: Optional[str] = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> AwsQuantumTask: @@ -223,12 +223,12 @@ def create( def __init__( self, arn: str, - aws_session: Optional[AwsSession] = None, + aws_session: AwsSession | None = None, poll_timeout_seconds: float = DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = DEFAULT_RESULTS_POLL_INTERVAL, logger: Logger = getLogger(__name__), quiet: bool = False, - task_specification: Optional[TaskSpecification] = None, + task_specification: TaskSpecification | None = None, **kwargs, ): """Initializes an `AwsQuantumTask`. @@ -548,7 +548,7 @@ def _create_internal( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], + device_parameters: dict[str, str] | BraketSchemaBase, disable_qubit_rewiring: bool, inputs: dict[str, float], gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -565,7 +565,7 @@ def _( create_task_kwargs: dict[str, Any], _device_arn: str, # Not currently used for OpenQasmProgram - _device_parameters: Union[dict, BraketSchemaBase], + _device_parameters: dict[str, str] | BraketSchemaBase, _disable_qubit_rewiring: bool, inputs: dict[str, float], _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -588,7 +588,7 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], + device_parameters: dict | BraketSchemaBase, _disable_qubit_rewiring: bool, inputs: dict[str, float], _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -625,10 +625,10 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], + device_parameters: dict | BraketSchemaBase, _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None, *args, **kwargs, ) -> AwsQuantumTask: @@ -653,10 +653,10 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], _device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], + _device_parameters: dict | BraketSchemaBase, _disable_qubit_rewiring: bool, _inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None, *args, **kwargs, ): @@ -675,10 +675,10 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], _device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], + _device_parameters: dict | BraketSchemaBase, _disable_qubit_rewiring: bool, _inputs: dict[str, float], - _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None, *args, **kwargs, ): @@ -694,9 +694,9 @@ def _( def _( blackbird_program: BlackbirdProgram, aws_session: AwsSession, - create_task_kwargs: dict[str, Any], - _device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], + create_task_kwargs: dict[str, any], + device_arn: str, + _device_parameters: dict | BraketSchemaBase, _disable_qubit_rewiring: bool, _inputs: dict[str, float], _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -714,7 +714,7 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[dict, BraketSchemaBase], + device_parameters: dict | BraketSchemaBase, disable_qubit_rewiring: bool, inputs: dict[str, float], gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], @@ -766,12 +766,13 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], device_arn: str, - device_parameters: Union[ - dict, DwaveDeviceParameters, DwaveAdvantageDeviceParameters, Dwave2000QDeviceParameters - ], + device_parameters: dict + | DwaveDeviceParameters + | DwaveAdvantageDeviceParameters + | Dwave2000QDeviceParameters, _disable_qubit_rewiring: bool, _inputs: dict[str, float], - _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None, *args, **kwargs, ) -> AwsQuantumTask: @@ -791,10 +792,10 @@ def _( aws_session: AwsSession, create_task_kwargs: dict[str, Any], _device_arn: str, - _device_parameters: Union[dict, BraketSchemaBase], - _disable_qubit_rewiring: bool, + _device_parameters: dict, + _disable_qubit_rewiring: AnalogHamiltonianSimulationTaskResult, _inputs: dict[str, float], - _gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + _gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None, *args, **kwargs, ) -> AwsQuantumTask: diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 50f19b675..792b01a5a 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -16,7 +16,7 @@ import time from concurrent.futures.thread import ThreadPoolExecutor from itertools import repeat -from typing import Any, Optional +from typing import Any from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram @@ -58,14 +58,13 @@ def __init__( max_workers: int = MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, gate_definitions: ( - Optional[ - dict[tuple[Gate, QubitSet], PulseSequence] - | list[dict[tuple[Gate, QubitSet], PulseSequence]] - ] + dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] + | None ) = None, - reservation_arn: Optional[str] = None, + reservation_arn: str | None = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, ): @@ -142,11 +141,10 @@ def __init__( @staticmethod def _tasks_inputs_gatedefs( task_specifications: TaskSpecification | list[TaskSpecification], - inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, - gate_definitions: Optional[ - dict[tuple[Gate, QubitSet], PulseSequence] - | list[dict[tuple[Gate, QubitSet], PulseSequence]], - ] = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] + | None = None, ) -> list[ tuple[ TaskSpecification, @@ -158,11 +156,7 @@ def _tasks_inputs_gatedefs( gate_definitions = gate_definitions or {} single_task_type = ( - Circuit, - Problem, - OpenQasmProgram, - BlackbirdProgram, - AnalogHamiltonianSimulation, + Circuit | Problem | OpenQasmProgram | BlackbirdProgram | AnalogHamiltonianSimulation ) single_input_type = dict single_gate_definitions_type = dict @@ -172,7 +166,7 @@ def _tasks_inputs_gatedefs( batch_length = 1 arg_lengths = [] - for arg, single_arg_type in zip(args, single_arg_types): + for arg, single_arg_type in zip(args, single_arg_types, strict=False): arg_length = 1 if isinstance(arg, single_arg_type) else len(arg) arg_lengths.append(arg_length) @@ -185,10 +179,10 @@ def _tasks_inputs_gatedefs( batch_length = arg_length for i in range(len(arg_lengths)): - if isinstance(args[i], (dict, single_task_type)): + if isinstance(args[i], dict | single_task_type): args[i] = repeat(args[i], batch_length) - tasks_inputs_definitions = list(zip(*args)) + tasks_inputs_definitions = list(zip(*args, strict=False)) for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): @@ -211,14 +205,13 @@ def _execute( max_workers: int = MAX_CONNECTIONS_DEFAULT, poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, gate_definitions: ( - Optional[ - dict[tuple[Gate, QubitSet], PulseSequence] - | list[dict[tuple[Gate, QubitSet], PulseSequence]] - ] + dict[tuple[Gate, QubitSet], PulseSequence] + | list[dict[tuple[Gate, QubitSet], PulseSequence]] + | None ) = None, - reservation_arn: Optional[str] = None, + reservation_arn: str | None = None, *args, **kwargs, ) -> list[AwsQuantumTask]: @@ -270,8 +263,8 @@ def _create_task( s3_destination_folder: AwsSession.S3DestinationFolder, shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, - inputs: Optional[dict[str, float]] = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] = None, + inputs: dict[str, float] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, reservation_arn: str | None = None, *args, **kwargs, @@ -326,7 +319,9 @@ def results( if not self._results or not use_cached_value: self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) self._unsuccessful = { - task.id for task, result in zip(self._tasks, self._results) if not result + task.id + for task, result in zip(self._tasks, self._results, strict=False) + if not result } retries = 0 @@ -376,14 +371,16 @@ def retry_unsuccessful_tasks(self) -> bool: *self._aws_quantum_task_args, **self._aws_quantum_task_kwargs, ) - for index, task in zip(unsuccessful_indices, retried_tasks): + for index, task in zip(unsuccessful_indices, retried_tasks, strict=False): self._tasks[index] = task retried_results = AwsQuantumTaskBatch._retrieve_results(retried_tasks, self._max_workers) - for index, result in zip(unsuccessful_indices, retried_results): + for index, result in zip(unsuccessful_indices, retried_results, strict=False): self._results[index] = result self._unsuccessful = { - task.id for task, result in zip(retried_tasks, retried_results) if not result + task.id + for task, result in zip(retried_tasks, retried_results, strict=False) + if not result } return not self._unsuccessful diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 7f502c336..35163970a 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -20,7 +20,7 @@ import warnings from functools import cache from pathlib import Path -from typing import Any, NamedTuple, Optional +from typing import Any, NamedTuple import backoff import boto3 @@ -45,10 +45,10 @@ class S3DestinationFolder(NamedTuple): def __init__( self, - boto_session: Optional[boto3.Session] = None, - braket_client: Optional[client] = None, - config: Optional[Config] = None, - default_bucket: Optional[str] = None, + boto_session: boto3.Session | None = None, + braket_client: client | None = None, + config: Config | None = None, + default_bucket: str | None = None, ): """Initializes an `AwsSession`. @@ -638,11 +638,11 @@ def get_device(self, arn: str) -> dict[str, Any]: def search_devices( self, - arns: Optional[list[str]] = None, - names: Optional[list[str]] = None, - types: Optional[list[str]] = None, - statuses: Optional[list[str]] = None, - provider_names: Optional[list[str]] = None, + arns: list[str] | None = None, + names: list[str] | None = None, + types: list[str] | None = None, + statuses: list[str] | None = None, + provider_names: list[str] | None = None, ) -> list[dict[str, Any]]: """Get devices based on filters. The result is the AND of all the filters `arns`, `names`, `types`, `statuses`, `provider_names`. @@ -747,8 +747,8 @@ def describe_log_streams( self, log_group: str, log_stream_prefix: str, - limit: Optional[int] = None, - next_token: Optional[str] = None, + limit: int | None = None, + next_token: str | None = None, ) -> dict[str, Any]: """Describes CloudWatch log streams in a log group with a given prefix. @@ -783,7 +783,7 @@ def get_log_events( log_stream: str, start_time: int, start_from_head: bool = True, - next_token: Optional[str] = None, + next_token: str | None = None, ) -> dict[str, Any]: """Gets CloudWatch log events from a given log stream. @@ -813,8 +813,8 @@ def get_log_events( def copy_session( self, - region: Optional[str] = None, - max_connections: Optional[int] = None, + region: str | None = None, + max_connections: int | None = None, ) -> AwsSession: """Creates a new AwsSession based on the region. @@ -868,8 +868,8 @@ def get_full_image_tag(self, image_uri: str) -> str: Returns: str: Verbose image tag for given image. """ - registry = image_uri.split(".")[0] - repository, tag = image_uri.split("/")[-1].split(":") + registry = image_uri.split(".", maxsplit=1)[0] + repository, tag = image_uri.rsplit("/", maxsplit=1)[-1].split(":") # get image digest of latest image digest = self.ecr_client.batch_get_image( diff --git a/src/braket/aws/queue_information.py b/src/braket/aws/queue_information.py index 77e5f3554..d00988398 100644 --- a/src/braket/aws/queue_information.py +++ b/src/braket/aws/queue_information.py @@ -13,7 +13,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Optional class QueueType(str, Enum): @@ -59,8 +58,8 @@ class QuantumTaskQueueInfo: """ queue_type: QueueType - queue_position: Optional[str] = None - message: Optional[str] = None + queue_position: str | None = None + message: str | None = None @dataclass @@ -77,5 +76,5 @@ class HybridJobQueueInfo: if 'queue_position' is None. Default: None. """ - queue_position: Optional[str] = None - message: Optional[str] = None + queue_position: str | None = None + message: str | None = None diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index ee2f30423..ff7d2efdf 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -17,7 +17,6 @@ import math from collections.abc import Sequence from functools import singledispatch -from typing import Optional from sympy import Float @@ -32,7 +31,7 @@ class AngledGate(Gate, Parameterizable): def __init__( self, angle: FreeParameterExpression | float, - qubit_count: Optional[int], + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Initializes an `AngledGate`. @@ -130,7 +129,7 @@ def __init__( self, angle_1: FreeParameterExpression | float, angle_2: FreeParameterExpression | float, - qubit_count: Optional[int], + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Inits a `DoubleAngledGate`. @@ -243,7 +242,7 @@ def __init__( angle_1: FreeParameterExpression | float, angle_2: FreeParameterExpression | float, angle_3: FreeParameterExpression | float, - qubit_count: Optional[int], + qubit_count: int | None, ascii_symbols: Sequence[str], ): """Inits a `TripleAngledGate`. @@ -382,7 +381,7 @@ def angled_ascii_characters(gate: str, angle: FreeParameterExpression | float) - str: Returns the ascii representation for an angled gate. """ - return f"{gate}({angle:{'.2f' if isinstance(angle, (float, Float)) else ''}})" + return f"{gate}({angle:{'.2f' if isinstance(angle, float | Float) else ''}})" def _multi_angled_ascii_characters( @@ -409,7 +408,7 @@ def format_string(angle: FreeParameterExpression | float) -> str: Returns: str: The ASCII representation of the angle. """ - return ".2f" if isinstance(angle, (float, Float)) else "" + return ".2f" if isinstance(angle, float | Float) else "" return f"{gate}({', '.join(f'{angle:{format_string(angle)}}' for angle in angles)})" diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index 86578fc89..b1e55ed88 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -1,13 +1,12 @@ from __future__ import annotations from functools import singledispatch -from typing import Optional, Union import numpy as np class BasisState: - def __init__(self, state: BasisStateInput, size: Optional[int] = None): + def __init__(self, state: BasisStateInput, size: int | None = None): self.state = _as_tuple(state, size) @property @@ -48,7 +47,7 @@ def __getitem__(self, item: int): return BasisState(self.state[item]) -BasisStateInput = Union[int, list[int], str, BasisState] +BasisStateInput = int | list[int] | str | BasisState @singledispatch diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index a93bf0994..9159a6ad2 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Optional, Union import numpy as np from braket.default_simulator.openqasm.program_context import AbstractProgramContext @@ -32,7 +31,7 @@ class BraketProgramContext(AbstractProgramContext): - def __init__(self, circuit: Optional[Circuit] = None): + def __init__(self, circuit: Circuit | None = None): """Inits a `BraketProgramContext`. Args: @@ -143,9 +142,7 @@ def add_result(self, result: Results) -> None: """ self._circuit.add_result_type(braket_result_to_result_type(result)) - def handle_parameter_value( - self, value: Union[float, Expr] - ) -> Union[float, FreeParameterExpression]: + def handle_parameter_value(self, value: float | Expr) -> float | FreeParameterExpression: """Convert parameter value to required format. Args: @@ -163,7 +160,7 @@ def handle_parameter_value( return value def add_measure( - self, target: tuple[int], classical_targets: Optional[Iterable[int]] = None + self, target: tuple[int], classical_targets: Iterable[int] | None = None ) -> None: """Add a measure instruction to the circuit diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index c8b19e146..b828bc80e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -636,7 +636,7 @@ def add_circuit( if target is not None: keys = sorted(circuit.qubits) values = target - target_mapping = dict(zip(keys, values)) + target_mapping = dict(zip(keys, values, strict=False)) for instruction in circuit.instructions: self.add_instruction(instruction, target_mapping=target_mapping) @@ -702,7 +702,7 @@ def add_verbatim_box( if target is not None: keys = sorted(verbatim_circuit.qubits) values = target - target_mapping = dict(zip(keys, values)) + target_mapping = dict(zip(keys, values, strict=False)) if verbatim_circuit.result_types: raise ValueError("Verbatim subcircuit is not measured and cannot have result types") @@ -1440,7 +1440,7 @@ def _generate_frame_wf_defcal_declarations( # Corresponding defcals with fixed arguments have been added # in _get_frames_waveforms_from_instrs if isinstance(gate, Parameterizable) and any( - not isinstance(parameter, (float, int, complex)) + not isinstance(parameter, float | int | complex) for parameter in gate.parameters ): continue @@ -1548,7 +1548,7 @@ def _add_fixed_argument_calibrations( ) additional_calibrations[bound_key] = calibration(**{ p.name if isinstance(p, FreeParameterExpression) else p: v - for p, v in zip(gate.parameters, instruction.operator.parameters) + for p, v in zip(gate.parameters, instruction.operator.parameters, strict=False) }) return additional_calibrations diff --git a/src/braket/circuits/circuit_helpers.py b/src/braket/circuits/circuit_helpers.py index 933a62fda..a8f17e1f2 100644 --- a/src/braket/circuits/circuit_helpers.py +++ b/src/braket/circuits/circuit_helpers.py @@ -39,7 +39,7 @@ def validate_circuit_and_shots(circuit: Circuit, shots: int) -> None: if not circuit.observables_simultaneously_measurable: raise ValueError("Observables cannot be sampled simultaneously") for rt in circuit.result_types: - if isinstance(rt, (ResultType.Amplitude, ResultType.StateVector)): + if isinstance(rt, ResultType.Amplitude | ResultType.StateVector): raise ValueError("StateVector or Amplitude cannot be specified when shots>0") # noqa: TRY004 if isinstance(rt, ResultType.Probability): num_qubits = len(rt.target) or circuit.qubit_count diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py index 4dceeb4fb..854d1f98e 100644 --- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py +++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. from abc import abstractmethod -from typing import Optional, Union from braket.circuits.instruction import Instruction from braket.circuits.noise_model.criteria import Criteria @@ -39,7 +38,7 @@ def instruction_matches(self, instruction: Instruction) -> bool: @staticmethod def _check_target_in_qubits( - qubits: Optional[set[Union[int, tuple[int]]]], target: QubitSetInput + qubits: set[int | tuple[int]] | None, target: QubitSetInput ) -> bool: """Returns true if the given targets of an instruction match the given qubit input set. diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py index 456867ce2..fbc237252 100644 --- a/src/braket/circuits/noise_model/criteria_input_parsing.py +++ b/src/braket/circuits/noise_model/criteria_input_parsing.py @@ -12,15 +12,14 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Optional, Union from braket.circuits.quantum_operator import QuantumOperator from braket.registers.qubit_set import QubitSetInput def parse_operator_input( - operators: Union[QuantumOperator, Iterable[QuantumOperator]], -) -> Optional[set[QuantumOperator]]: + operators: QuantumOperator | Iterable[QuantumOperator], +) -> set[QuantumOperator] | None: """Processes the quantum operator input to __init__ to validate and return a set of QuantumOperators. @@ -46,8 +45,8 @@ def parse_operator_input( def parse_qubit_input( - qubits: Optional[QubitSetInput], expected_qubit_count: Optional[int] = 0 -) -> Optional[set[Union[int, tuple[int]]]]: + qubits: QubitSetInput | None, expected_qubit_count: int | None = 0 +) -> set[int | tuple[int]] | None: """Processes the qubit input to __init__ to validate and return a set of qubit targets. Args: diff --git a/src/braket/circuits/noise_model/gate_criteria.py b/src/braket/circuits/noise_model/gate_criteria.py index 7870e9b6b..0ab9cf744 100644 --- a/src/braket/circuits/noise_model/gate_criteria.py +++ b/src/braket/circuits/noise_model/gate_criteria.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction @@ -30,8 +30,8 @@ class GateCriteria(CircuitInstructionCriteria): def __init__( self, - gates: Optional[Union[Gate, Iterable[Gate]]] = None, - qubits: Optional[QubitSetInput] = None, + gates: Gate | Iterable[Gate] | None = None, + qubits: QubitSetInput | None = None, ): """Creates Gate-based Criteria. See instruction_matches() for more details. @@ -66,7 +66,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: """ return [CriteriaKey.QUBIT, CriteriaKey.GATE] - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Gets the keys for a given CriteriaKey. Args: diff --git a/src/braket/circuits/noise_model/measure_criteria.py b/src/braket/circuits/noise_model/measure_criteria.py index d11b528bd..7c0a8b3e1 100644 --- a/src/braket/circuits/noise_model/measure_criteria.py +++ b/src/braket/circuits/noise_model/measure_criteria.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any from braket.circuits.instruction import Instruction from braket.circuits.measure import Measure @@ -25,7 +25,7 @@ class MeasureCriteria(CircuitInstructionCriteria): """This class models noise Criteria based on Measure instructions.""" - def __init__(self, qubits: Optional[QubitSetInput] = None): + def __init__(self, qubits: QubitSetInput | None = None): """Creates Measure-based Criteria. Args: @@ -48,7 +48,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: """ return [CriteriaKey.QUBIT] - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Gets the keys for a given CriteriaKey. Args: diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py index 5cb510f2e..671fa1808 100644 --- a/src/braket/circuits/noise_model/observable_criteria.py +++ b/src/braket/circuits/noise_model/observable_criteria.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult from braket.circuits.noise_model.criteria_input_parsing import ( @@ -30,8 +30,8 @@ class ObservableCriteria(ResultTypeCriteria): def __init__( self, - observables: Optional[Union[Observable, Iterable[Observable]]] = None, - qubits: Optional[QubitSetInput] = None, + observables: Observable | Iterable[Observable] | None = None, + qubits: QubitSetInput | None = None, ): """Creates Observable-based Criteria. See instruction_matches() for more details. @@ -72,7 +72,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: """ return [CriteriaKey.OBSERVABLE, CriteriaKey.QUBIT] - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Gets the keys for a given CriteriaKey. Args: diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index 26594ca60..cbd63c8e7 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult from braket.circuits.noise_model.criteria_input_parsing import parse_qubit_input @@ -23,7 +23,7 @@ class QubitInitializationCriteria(InitializationCriteria): """This class models initialization noise Criteria based on qubits.""" - def __init__(self, qubits: Optional[QubitSetInput] = None): + def __init__(self, qubits: QubitSetInput | None = None): """Creates initialization noise Qubit-based Criteria. Args: @@ -46,7 +46,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: """ return [CriteriaKey.QUBIT] - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Gets the keys for a given CriteriaKey. Args: diff --git a/src/braket/circuits/noise_model/unitary_gate_criteria.py b/src/braket/circuits/noise_model/unitary_gate_criteria.py index 34b348e2e..f70abb7e6 100644 --- a/src/braket/circuits/noise_model/unitary_gate_criteria.py +++ b/src/braket/circuits/noise_model/unitary_gate_criteria.py @@ -12,7 +12,7 @@ # language governing permissions and limitations under the License. from collections.abc import Iterable -from typing import Any, Optional, Union +from typing import Any from braket.circuits.gates import Unitary from braket.circuits.instruction import Instruction @@ -25,7 +25,7 @@ class UnitaryGateCriteria(CircuitInstructionCriteria): """This class models noise Criteria based on unitary gates represented as a matrix.""" - def __init__(self, unitary: Unitary, qubits: Optional[QubitSetInput] = None): + def __init__(self, unitary: Unitary, qubits: QubitSetInput | None = None): """Creates unitary gate-based Criteria. See instruction_matches() for more details. Args: @@ -55,7 +55,7 @@ def applicable_key_types(self) -> Iterable[CriteriaKey]: """ return [CriteriaKey.QUBIT, CriteriaKey.UNITARY_GATE] - def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: + def get_keys(self, key_type: CriteriaKey) -> CriteriaKeyResult | set[Any]: """Gets the keys for a given CriteriaKey. Args: diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 777cfa81a..a0c9114e6 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -13,7 +13,7 @@ import itertools from collections.abc import Iterable -from typing import Any, ClassVar, Union +from typing import Any, ClassVar import braket.ir.jaqcd as ir import numpy as np @@ -78,7 +78,7 @@ class BitFlip(SingleProbabilisticNoise): This noise channel is shown as `BF` in circuit diagrams. """ - def __init__(self, probability: Union[FreeParameterExpression, float]): + def __init__(self, probability: FreeParameterExpression | float): super().__init__( probability=probability, qubit_count=None, @@ -128,7 +128,7 @@ def bit_flip(target: QubitSetInput, probability: float) -> Iterable[Instruction] for qubit in QubitSet(target) ] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Noise: + def bind_values(self, **kwargs: FreeParameter | str) -> Noise: """Takes in parameters and attempts to assign them to values. Args: @@ -183,7 +183,7 @@ class PhaseFlip(SingleProbabilisticNoise): This noise channel is shown as `PF` in circuit diagrams. """ - def __init__(self, probability: Union[FreeParameterExpression, float]): + def __init__(self, probability: FreeParameterExpression | float): super().__init__( probability=probability, qubit_count=None, @@ -233,7 +233,7 @@ def phase_flip(target: QubitSetInput, probability: float) -> Iterable[Instructio for qubit in QubitSet(target) ] - def bind_values(self, **kwargs: Union[FreeParameter, str]) -> Noise: + def bind_values(self, **kwargs: FreeParameter | str) -> Noise: """Takes in parameters and attempts to assign them to values. Args: @@ -306,9 +306,9 @@ class PauliChannel(PauliNoise): def __init__( self, - probX: Union[FreeParameterExpression, float], - probY: Union[FreeParameterExpression, float], - probZ: Union[FreeParameterExpression, float], + probX: FreeParameterExpression | float, + probY: FreeParameterExpression | float, + probZ: FreeParameterExpression | float, ): """Creates PauliChannel noise. @@ -459,7 +459,7 @@ class Depolarizing(SingleProbabilisticNoise_34): This noise channel is shown as `DEPO` in circuit diagrams. """ - def __init__(self, probability: Union[FreeParameterExpression, float]): + def __init__(self, probability: FreeParameterExpression | float): super().__init__( probability=probability, qubit_count=None, @@ -584,7 +584,7 @@ class TwoQubitDepolarizing(SingleProbabilisticNoise_1516): This noise channel is shown as `DEPO` in circuit diagrams. """ - def __init__(self, probability: Union[FreeParameterExpression, float]): + def __init__(self, probability: FreeParameterExpression | float): super().__init__( probability=probability, qubit_count=None, @@ -709,7 +709,7 @@ class TwoQubitDephasing(SingleProbabilisticNoise_34): This noise channel is shown as `DEPH` in circuit diagrams. """ - def __init__(self, probability: Union[FreeParameterExpression, float]): + def __init__(self, probability: FreeParameterExpression | float): super().__init__( probability=probability, qubit_count=None, @@ -988,7 +988,7 @@ class AmplitudeDamping(DampingNoise): This noise channel is shown as `AD` in circuit diagrams. """ - def __init__(self, gamma: Union[FreeParameterExpression, float]): + def __init__(self, gamma: FreeParameterExpression | float): super().__init__( gamma=gamma, qubit_count=None, @@ -1104,8 +1104,8 @@ class GeneralizedAmplitudeDamping(GeneralizedAmplitudeDampingNoise): def __init__( self, - gamma: Union[FreeParameterExpression, float], - probability: Union[FreeParameterExpression, float], + gamma: FreeParameterExpression | float, + probability: FreeParameterExpression | float, ): super().__init__( gamma=gamma, @@ -1231,7 +1231,7 @@ class PhaseDamping(DampingNoise): This noise channel is shown as `PD` in circuit diagrams. """ - def __init__(self, gamma: Union[FreeParameterExpression, float]): + def __init__(self, gamma: FreeParameterExpression | float): super().__init__( gamma=gamma, qubit_count=None, @@ -1438,9 +1438,7 @@ def from_dict(cls, noise: dict) -> Noise: Noise.register_noise(Kraus) -def _ascii_representation( - noise: str, parameters: list[Union[FreeParameterExpression, float]] -) -> str: +def _ascii_representation(noise: str, parameters: list[FreeParameterExpression | float]) -> str: """Generates a formatted ascii representation of a noise. Args: @@ -1458,11 +1456,11 @@ def _ascii_representation( return f"{noise}({param_str})" -def _substitute_value(expr: float, **kwargs) -> Union[FreeParameterExpression, float]: +def _substitute_value(expr: float, **kwargs) -> FreeParameterExpression | float: return expr.subs(kwargs) if isinstance(expr, FreeParameterExpression) else expr -def _parameter_from_dict(parameter: Union[dict, float]) -> Union[FreeParameter, float]: +def _parameter_from_dict(parameter: dict | float) -> FreeParameter | float: """Converts a parameter from a dictionary if it's a FreeParameter, otherwise returns the float. Args: diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index f5a2ce393..c05801fe9 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -571,7 +571,7 @@ def _to_openqasm( raise ValueError( f"Invalid target of length {len(target)} for Sum with {len(self.summands)} terms" ) - for i, (term, term_target) in enumerate(zip(self.summands, target)): + for i, (term, term_target) in enumerate(zip(self.summands, target, strict=False)): if term.qubit_count != len(term_target): raise ValueError( f"Invalid target for term {i} of Sum. " @@ -583,7 +583,7 @@ def _to_openqasm( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) - for obs, term_target in zip(self.summands, target) + for obs, term_target in zip(self.summands, target, strict=False) ).replace("+ -", "- ") @property diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index eadd4c02d..a59383c71 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -216,7 +216,7 @@ def __init__( "target length is equal to the observable term's qubits count." ) self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(self._target, observable.summands): + for term_target, obs in zip(self._target, observable.summands, strict=False): if obs.qubit_count != len(term_target): raise ValueError( "Sum observable's target shape must be a nested list where each term's " diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index c7e3dc8b3..e8e06dd44 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -124,7 +124,7 @@ def _create_diagram_column( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = dict(zip(control_qubits, control_state)) + map_control_qubit_states = dict(zip(control_qubits, control_state, strict=False)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 0bddd4c36..1e3dcae6b 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -131,7 +131,7 @@ def _group_items( # Can only print QuantumOperator and CompilerDirective operators for instructions at # the moment if isinstance(item, Instruction) and not isinstance( - item.operator, (CompilerDirective, QuantumOperator) + item.operator, CompilerDirective | QuantumOperator ): continue diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 6285fd0c1..6dff139b4 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -164,7 +164,7 @@ def _build_parameters( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = dict(zip(control_qubits, control_state)) + map_control_qubit_states = dict(zip(control_qubits, control_state, strict=False)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 3bddd0ca7..db039999e 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -15,7 +15,7 @@ import warnings from abc import ABC, abstractmethod -from typing import Any, Optional +from typing import Any from braket.device_schema import DeviceActionType @@ -43,8 +43,8 @@ def __init__(self, name: str, status: str): def run( self, task_specification: TaskSpecification, - shots: Optional[int], - inputs: Optional[dict[str, float]], + shots: int | None, + inputs: dict[str, float] | None, *args, **kwargs, ) -> QuantumTask: @@ -70,9 +70,9 @@ def run( def run_batch( self, task_specifications: TaskSpecification | list[TaskSpecification], - shots: Optional[int], - max_parallel: Optional[int], - inputs: Optional[dict[str, float] | list[dict[str, float]]], + shots: int | None, + max_parallel: int | None, + inputs: dict[str, float] | list[dict[str, float]] | None, *args: Any, **kwargs: Any, ) -> QuantumTaskBatch: diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 7762c9da8..984044706 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -17,7 +17,7 @@ from functools import singledispatchmethod from itertools import repeat from os import cpu_count -from typing import Any, Optional +from typing import Any from braket.device_schema import DeviceActionType, DeviceCapabilities from braket.ir.ahs import Program as AHSProgram @@ -66,7 +66,7 @@ class LocalSimulator(Device): def __init__( self, backend: str | BraketSimulator = "default", - noise_model: Optional[NoiseModel] = None, + noise_model: NoiseModel | None = None, ): """Initializes a `LocalSimulator`. @@ -92,7 +92,7 @@ def run( self, task_specification: TaskSpecification, shots: int | None = None, - inputs: Optional[dict[str, float]] = None, + inputs: dict[str, float] | None = None, *args: Any, **kwargs: Any, ) -> LocalQuantumTask: @@ -135,9 +135,9 @@ def run( def run_batch( self, task_specifications: TaskSpecification | list[TaskSpecification], - shots: Optional[int] = 0, - max_parallel: Optional[int] = None, - inputs: Optional[dict[str, float] | list[dict[str, float]]] = None, + shots: int | None = 0, + max_parallel: int | None = None, + inputs: dict[str, float] | list[dict[str, float]] | None = None, *args, **kwargs, ) -> LocalQuantumTaskBatch: @@ -173,7 +173,7 @@ def run_batch( single_task = isinstance( task_specifications, - (Circuit, OpenQASMProgram, Problem, AnalogHamiltonianSimulation), + Circuit | OpenQASMProgram | Problem | AnalogHamiltonianSimulation, ) single_input = isinstance(inputs, dict) @@ -186,7 +186,7 @@ def run_batch( if single_input: inputs = repeat(inputs) - tasks_and_inputs = zip(task_specifications, inputs) + tasks_and_inputs = zip(task_specifications, inputs, strict=False) if single_task and single_input: tasks_and_inputs = [next(tasks_and_inputs)] @@ -255,7 +255,7 @@ def _construct_payload( raise NotImplementedError(f"Unsupported task type {type(task_specification)}") @_construct_payload.register - def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: int): + def _(self, circuit: Circuit, inputs: dict[str, float] | None, shots: int): simulator = self._delegate if DeviceActionType.OPENQASM in simulator.properties.action: validate_circuit_and_shots(circuit, shots) @@ -268,7 +268,7 @@ def _(self, circuit: Circuit, inputs: Optional[dict[str, float]], shots: int): raise NotImplementedError(f"{type(simulator)} does not support qubit gate-based programs") @_construct_payload.register - def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots: int): + def _(self, program: OpenQASMProgram, inputs: dict[str, float] | None, _shots: int): simulator = self._delegate if DeviceActionType.OPENQASM not in simulator.properties.action: raise NotImplementedError(f"{type(simulator)} does not support OpenQASM programs") @@ -282,7 +282,7 @@ def _(self, program: OpenQASMProgram, inputs: Optional[dict[str, float]], _shots return program @_construct_payload.register - def _(self, program_set: ProgramSet, inputs: Optional[dict[str, float]], _shots: int): + def _(self, program_set: ProgramSet, inputs: dict[str, float] | None, _shots: int): if inputs: raise ValueError( "Inputs for program sets must be provided in the program set object. " @@ -292,7 +292,7 @@ def _(self, program_set: ProgramSet, inputs: Optional[dict[str, float]], _shots: return program_set.to_ir() @_construct_payload.register - def _(self, program_set: OpenQASMProgramSet, inputs: Optional[dict[str, float]], _shots: int): + def _(self, program_set: OpenQASMProgramSet, inputs: dict[str, float] | None, _shots: int): if inputs: raise ValueError( "Inputs for program sets must be provided in the program set object. " @@ -302,7 +302,7 @@ def _(self, program_set: OpenQASMProgramSet, inputs: Optional[dict[str, float]], return program_set @_construct_payload.register - def _(self, program: SerializableProgram, inputs: Optional[dict[str, float]], _shots: int): + def _(self, program: SerializableProgram, inputs: dict[str, float] | None, _shots: int): inputs_copy = inputs.copy() if inputs is not None else {} return OpenQASMProgram(source=program.to_ir(ir_type=IRType.OPENQASM), inputs=inputs_copy) @@ -346,9 +346,7 @@ def _(self, result: GateModelTaskResult, **kwargs) -> GateModelQuantumTaskResult return GateModelQuantumTaskResult.from_object(result) @_to_result_object.register - def _( - self, result: ProgramSetTaskResult, task_specification: Optional[TaskSpecification] = None - ): + def _(self, result: ProgramSetTaskResult, task_specification: TaskSpecification | None = None): return ProgramSetQuantumTaskResult.from_object(result, task_specification) @_to_result_object.register @@ -363,7 +361,7 @@ def _(self, result: AnnealingTaskResult, **kwargs) -> AnnealingQuantumTaskResult @staticmethod def _default_shots(task_specification: TaskSpecification) -> int: - if isinstance(task_specification, (ProgramSet, OpenQASMProgramSet)): + if isinstance(task_specification, ProgramSet | OpenQASMProgramSet): if not task_specification.shots_per_executable: raise ValueError("Shots must be specified in program set or during task creation") return task_specification.total_shots diff --git a/src/braket/experimental_capabilities/iqm/classical_control.py b/src/braket/experimental_capabilities/iqm/classical_control.py index 0bfd4318f..4a4d4f618 100644 --- a/src/braket/experimental_capabilities/iqm/classical_control.py +++ b/src/braket/experimental_capabilities/iqm/classical_control.py @@ -14,7 +14,7 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Any, Optional +from typing import Any from braket.circuits import circuit from braket.circuits.angled_gate import _multi_angled_ascii_characters @@ -98,9 +98,9 @@ def parameters(self) -> list[FreeParameterExpression | float | int]: def to_ir( self, - target: Optional[QubitSet] = None, + target: QubitSet | None = None, ir_type: IRType = IRType.OPENQASM, - serialization_properties: Optional[SerializationProperties] = None, + serialization_properties: SerializationProperties | None = None, **kwargs: Any, ) -> Any: """Convert this operator to its IR representation. diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index cda249535..24cc3bacc 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -386,7 +386,7 @@ def _validate_python_version(image_uri: str | None, aws_session: AwsSession | No def _process_dependencies(dependencies: str | Path | list[str], temp_dir: Path) -> None: - if isinstance(dependencies, (str, Path)): + if isinstance(dependencies, str | Path): # requirements file shutil.copy(Path(dependencies).resolve(), temp_dir / "requirements.txt") else: @@ -398,7 +398,7 @@ def _process_dependencies(dependencies: str | Path | list[str], temp_dir: Path) class _IncludeModules: def __init__(self, modules: str | ModuleType | Iterable[str | ModuleType] | None = None): modules = modules or [] - if isinstance(modules, (str, ModuleType)): + if isinstance(modules, str | ModuleType): modules = [modules] self._modules = [ (importlib.import_module(module) if isinstance(module, str) else module) diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 45951d646..ce963db3b 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -20,7 +20,7 @@ # Support for reading logs # ############################################################################## -from typing import ClassVar, NamedTuple, Optional +from typing import ClassVar, NamedTuple from botocore.exceptions import ClientError @@ -165,7 +165,7 @@ def flush_log_streams( has_streams: bool, color_wrap: ColorWrap, state: list[str], - queue_position: Optional[str] = None, + queue_position: str | None = None, ) -> bool: """Flushes log streams to stdout. diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index b6e331142..e27ecb6cf 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -13,7 +13,6 @@ import time from logging import Logger, getLogger -from typing import Union from braket.aws.aws_session import AwsSession from braket.jobs.metrics_data.definitions import MetricStatistic, MetricType @@ -122,7 +121,7 @@ def get_metrics_for_job( job_name: str, metric_type: MetricType = MetricType.TIMESTAMP, statistic: MetricStatistic = MetricStatistic.MAX, - ) -> dict[str, list[Union[str, float, int]]]: + ) -> dict[str, list[str | float | int]]: """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. Args: diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index 43221d35f..4369a1520 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -59,7 +59,7 @@ def __init__(self, expression: FreeParameterExpression | Number | sympy.Expr | s } if isinstance(expression, FreeParameterExpression): self._expression = expression.expression - elif isinstance(expression, (Number, sympy.Expr)): + elif isinstance(expression, Number | sympy.Expr): self._expression = expression elif isinstance(expression, str): self._expression = self._parse_string_expression(expression).expression diff --git a/src/braket/program_sets/circuit_binding.py b/src/braket/program_sets/circuit_binding.py index ef640e436..a3a4265dc 100644 --- a/src/braket/program_sets/circuit_binding.py +++ b/src/braket/program_sets/circuit_binding.py @@ -14,7 +14,6 @@ from __future__ import annotations from collections.abc import Mapping, Sequence -from typing import Optional from braket.ir.openqasm import Program @@ -31,8 +30,8 @@ class CircuitBinding: def __init__( self, circuit: Circuit, - input_sets: Optional[ParameterSetsLike] = None, - observables: Optional[Sequence[Observable] | Sum] = None, + input_sets: ParameterSetsLike | None = None, + observables: Sequence[Observable] | Sum | None = None, ): """ A single parametrized circuit and multiple parameter sets and observables. @@ -80,14 +79,14 @@ def circuit(self) -> Circuit: return self._circuit @property - def input_sets(self) -> Optional[ParameterSets]: + def input_sets(self) -> ParameterSets | None: """ Optional[ParameterSets]: The inputs to the circuit, if specified. """ return self._input_sets @property - def observables(self) -> Optional[Sequence[Observable] | Sum]: + def observables(self) -> Sequence[Observable] | Sum | None: """ Optional[Sequence[Observable] | Sum]: The observables or qubit Hamiltonian to measure, if specified. diff --git a/src/braket/program_sets/parameter_sets.py b/src/braket/program_sets/parameter_sets.py index bd328dfb4..52726d763 100644 --- a/src/braket/program_sets/parameter_sets.py +++ b/src/braket/program_sets/parameter_sets.py @@ -164,4 +164,4 @@ def _strict_zip(*args) -> zip: length = len(next(it)) if not all(len(lst) == length for lst in it): raise ValueError("Lists must be of equal length") - return zip(*args) + return zip(*args, strict=False) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index 9442f5772..e787d0ce3 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -15,7 +15,7 @@ from collections import defaultdict from collections.abc import KeysView from dataclasses import dataclass -from typing import Any, ClassVar, Optional, Union +from typing import Any, ClassVar import numpy as np from openpulse import ast @@ -64,9 +64,7 @@ def __init__(self, program: Program, frames: dict[str, Frame]): self._qubit_frames_mapping: dict[str, list[str]] = _init_qubit_frame_mapping(frames) self.visit(program.to_ast(include_externs=False), context) - def visit( - self, node: Union[ast.QASMNode, ast.Expression], context: Optional[_ParseState] = None - ) -> Any: + def visit(self, node: ast.QASMNode | ast.Expression, context: _ParseState | None = None) -> Any: """Visit a node. Args: @@ -80,7 +78,7 @@ def visit( def _get_frame_parameters( self, parameters: list[ast.Expression], context: _ParseState - ) -> Union[KeysView, list[str]]: + ) -> KeysView | list[str]: frame_ids = set() for expression in parameters: identifier_name = self.visit(expression, context) @@ -129,7 +127,7 @@ def visit_ExpressionStatement(self, node: ast.ExpressionStatement, context: _Par def visit_ClassicalDeclaration( self, node: ast.ClassicalDeclaration, context: _ParseState - ) -> Union[dict, None]: + ) -> dict | None: """Visit a Classical Declaration. node.type, node.identifier, node.init_expression angle[20] a = 1+2; @@ -498,7 +496,7 @@ def play(self, node: ast.FunctionCall, context: _ParseState) -> None: frame_id = self.visit(node.arguments[0], context) if isinstance(node.arguments[1], ast.ArrayLiteral): amps = self.visit(node.arguments[1], context) - elif isinstance(node.arguments[1], (ast.Identifier, ast.FunctionCall)): + elif isinstance(node.arguments[1], ast.Identifier | ast.FunctionCall): amps = self.visit(node.arguments[1], context) if isinstance(amps, Waveform): amps = amps.sample(context.frame_data[frame_id].dt) diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index ce201f3c1..b4a7de590 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. import operator -from typing import Union from openpulse import ast from openqasm3.visitor import QASMTransformer @@ -28,9 +27,7 @@ def __init__(self, param_values: dict[str, float], program: Program): self.program = program super().__init__() - def visit_Identifier( - self, identifier: ast.Identifier - ) -> Union[ast.Identifier, ast.FloatLiteral]: + def visit_Identifier(self, identifier: ast.Identifier) -> ast.Identifier | ast.FloatLiteral: """Visit an Identifier. If the Identifier is used to hold a `FreeParameterExpression`, it will be simplified @@ -48,7 +45,7 @@ def visit_Identifier( def visit_BinaryExpression( self, node: ast.BinaryExpression - ) -> Union[ast.BinaryExpression, ast.FloatLiteral]: + ) -> ast.BinaryExpression | ast.FloatLiteral: """Visit a BinaryExpression. Visit the operands and simplify if they are literals. @@ -75,7 +72,7 @@ def visit_BinaryExpression( def visit_UnaryExpression( self, node: ast.UnaryExpression - ) -> Union[ast.UnaryExpression, ast.FloatLiteral]: + ) -> ast.UnaryExpression | ast.FloatLiteral: """Visit an UnaryExpression. Visit the operand and simplify if it is a literal. @@ -88,7 +85,7 @@ def visit_UnaryExpression( """ expression = self.visit(node.expression) if ( - isinstance(expression, (ast.FloatLiteral, ast.DurationLiteral)) + isinstance(expression, ast.FloatLiteral | ast.DurationLiteral) and node.op == ast.UnaryOperator["-"] ): return type(expression)(-expression.value) diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index 40a6d25d5..a0c278609 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Any, Optional +from typing import Any from openpulse import ast from openqasm3.visitor import QASMTransformer @@ -24,7 +24,7 @@ class _IRQASMTransformer(QASMTransformer): readout value to a bit register element. """ - def __init__(self, register_identifier: Optional[str] = None): + def __init__(self, register_identifier: str | None = None): self._register_identifier = register_identifier self._capture_v0_count = 0 super().__init__() diff --git a/src/braket/registers/qubit_set.py b/src/braket/registers/qubit_set.py index f8a6c7e45..e4e5a57cc 100644 --- a/src/braket/registers/qubit_set.py +++ b/src/braket/registers/qubit_set.py @@ -14,13 +14,13 @@ from __future__ import annotations from collections.abc import Iterable -from typing import Any, Union +from typing import Any from boltons.setutils import IndexedSet from braket.registers.qubit import Qubit, QubitInput -QubitSetInput = Union[QubitInput, Iterable[QubitInput]] +QubitSetInput = QubitInput | Iterable[QubitInput] def _flatten(other: Any) -> Any: diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index da8cdb583..4fdf6fc31 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -125,7 +125,8 @@ def get_counts(self) -> dict[str, int]: post = shot.post_sequence # converting presequence and postsequence measurements to state_idx state_idx = [ - 0 if pre_i == 0 else 1 if post_i == 0 else 2 for pre_i, post_i in zip(pre, post) + 0 if pre_i == 0 else 1 if post_i == 0 else 2 + for pre_i, post_i in zip(pre, post, strict=False) ] state = "".join(states[s_idx] for s_idx in state_idx) state_counts.update([state]) diff --git a/src/braket/tasks/program_set_quantum_task_result.py b/src/braket/tasks/program_set_quantum_task_result.py index d5f5b8152..330655f0b 100644 --- a/src/braket/tasks/program_set_quantum_task_result.py +++ b/src/braket/tasks/program_set_quantum_task_result.py @@ -17,7 +17,6 @@ from collections import Counter from collections.abc import Sequence from dataclasses import dataclass -from typing import Optional import boto3 import numpy as np @@ -334,7 +333,7 @@ class ProgramSetQuantumTaskResult: @staticmethod def from_object( - result_schema: ProgramSetTaskResult, program_set: Optional[ProgramSet] = None + result_schema: ProgramSetTaskResult, program_set: ProgramSet | None = None ) -> ProgramSetQuantumTaskResult: """ Create ProgramSetQuantumTaskResult from ProgramSetTaskResult object. diff --git a/src/braket/tasks/quantum_task.py b/src/braket/tasks/quantum_task.py index eb9edc314..df47f6666 100644 --- a/src/braket/tasks/quantum_task.py +++ b/src/braket/tasks/quantum_task.py @@ -13,7 +13,7 @@ import asyncio from abc import ABC, abstractmethod -from typing import Any, Union +from typing import Any from braket.ir.openqasm import Program as OpenQASMProgram from braket.ir.openqasm import ProgramSet as OpenQASMProgramSet @@ -26,20 +26,20 @@ from braket.tasks import AnalogHamiltonianSimulationQuantumTaskResult, ProgramSetQuantumTaskResult from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult -TaskSpecification = Union[ - Circuit, - SerializableProgram, - ProgramSet, - OpenQASMProgram, - OpenQASMProgramSet, - AnalogHamiltonianSimulation, - PulseSequence, -] -TaskResult = Union[ - GateModelQuantumTaskResult, - ProgramSetQuantumTaskResult, - AnalogHamiltonianSimulationQuantumTaskResult, -] +TaskSpecification = ( + Circuit + | SerializableProgram + | ProgramSet + | OpenQASMProgram + | OpenQASMProgramSet + | AnalogHamiltonianSimulation + | PulseSequence +) +TaskResult = ( + GateModelQuantumTaskResult + | ProgramSetQuantumTaskResult + | AnalogHamiltonianSimulationQuantumTaskResult +) class QuantumTask(ABC): diff --git a/src/braket/tasks/quantum_task_batch.py b/src/braket/tasks/quantum_task_batch.py index 790f6e19a..bfd829673 100644 --- a/src/braket/tasks/quantum_task_batch.py +++ b/src/braket/tasks/quantum_task_batch.py @@ -12,7 +12,6 @@ # language governing permissions and limitations under the License. from abc import ABC, abstractmethod -from typing import Union from braket.tasks.annealing_quantum_task_result import AnnealingQuantumTaskResult from braket.tasks.gate_model_quantum_task_result import GateModelQuantumTaskResult @@ -26,9 +25,7 @@ class QuantumTaskBatch(ABC): def results( self, ) -> list[ - Union[ - GateModelQuantumTaskResult, AnnealingQuantumTaskResult, PhotonicModelQuantumTaskResult - ] + GateModelQuantumTaskResult | AnnealingQuantumTaskResult | PhotonicModelQuantumTaskResult ]: """Get the quantum task results. diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 2cbf86df3..9b442218b 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -115,7 +115,7 @@ def from_lists(times: list[float], values: list[float]) -> TimeSeries: ) ts = TimeSeries() - for t, v in zip(times, values): + for t, v in zip(times, values, strict=False): ts.put(t, v) return ts @@ -181,7 +181,7 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: new_time_series = TimeSeries() new_times = self.times() + other.times() new_values = self.values() + other.values() - for t, v in zip(new_times, new_values): + for t, v in zip(new_times, new_values, strict=False): new_time_series.put(t, v) return new_time_series @@ -261,7 +261,7 @@ def stitch( ) new_values = [*self.values()[:-1], bndry_val, *other.values()[1:]] - for t, v in zip(new_times, new_values): + for t, v in zip(new_times, new_values, strict=False): new_time_series.put(t, v) return new_time_series From 632a690dd67ade1201827ef2cea5245c37ef60ef Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 20 Aug 2025 16:56:10 -0700 Subject: [PATCH 316/347] doc: Use latest for doc generation (#1110) --- .readthedocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 42f594159..4b5f0ebc2 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -15,9 +15,9 @@ formats: # setting up build.os and the python version build: - os: ubuntu-22.04 + os: ubuntu-lts-latest tools: - python: "3.10" + python: latest # Optionally set the version of Python and requirements required to build your docs python: From ba94525aa7a5aa2f084e8f8319632e8b22f8776d Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 20 Aug 2025 21:56:26 -0700 Subject: [PATCH 317/347] fix: Make `zip` strict (#1111) --- program.py | 4 - pydoclint-baseline.txt | 233 ------------------ .../ahs/analog_hamiltonian_simulation.py | 2 +- src/braket/ahs/driving_field.py | 2 +- src/braket/ahs/local_detuning.py | 2 +- src/braket/aws/aws_quantum_task_batch.py | 12 +- src/braket/circuits/circuit.py | 6 +- src/braket/circuits/observables.py | 4 +- src/braket/circuits/result_type.py | 2 +- .../ascii_circuit_diagram.py | 2 +- .../unicode_circuit_diagram.py | 2 +- src/braket/program_sets/parameter_sets.py | 13 +- src/braket/program_sets/program_set.py | 15 +- ...iltonian_simulation_quantum_task_result.py | 2 +- .../tasks/program_set_quantum_task_result.py | 3 +- src/braket/timings/time_series.py | 6 +- .../program_sets/test_parameter_sets.py | 8 +- 17 files changed, 34 insertions(+), 284 deletions(-) delete mode 100644 program.py delete mode 100644 pydoclint-baseline.txt diff --git a/program.py b/program.py deleted file mode 100644 index 45690eccf..000000000 --- a/program.py +++ /dev/null @@ -1,4 +0,0 @@ -from braket.aws import AwsDevice - -device = AwsDevice("arn:aws:braket::us-west-1:device/qpu/rigetti/Ankaa-2") -print(device) diff --git a/pydoclint-baseline.txt b/pydoclint-baseline.txt deleted file mode 100644 index 816c4265a..000000000 --- a/pydoclint-baseline.txt +++ /dev/null @@ -1,233 +0,0 @@ -src/braket/aws/aws_device.py - DOC101: Method `AwsDevice.run_batch`: Docstring contains fewer arguments than in function signature. - DOC103: Method `AwsDevice.run_batch`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**aws_quantum_task_kwargs: , *aws_quantum_task_args: ]. --------------------- -src/braket/aws/aws_quantum_job.py - DOC502: Method `AwsQuantumJob.create` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC101: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: Docstring contains fewer arguments than in function signature. - DOC109: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AwsQuantumJob._is_valid_aws_session_region_for_job_arn`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [aws_session: AwsSession, job_arn: str]. - DOC502: Method `AwsQuantumJob.logs` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC502: Method `AwsQuantumJob.cancel` has a "Raises" section in the docstring, but there are not "raise" statements in the body --------------------- -src/braket/aws/aws_quantum_task.py - DOC101: Method `AwsQuantumTask.create`: Docstring contains fewer arguments than in function signature. - DOC103: Method `AwsQuantumTask.create`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. - DOC501: Method `AwsQuantumTask.create` has "raise" statements, but the docstring does not have a "Raises" section - DOC101: Method `AwsQuantumTask._aws_session_for_task_arn`: Docstring contains fewer arguments than in function signature. - DOC109: Method `AwsQuantumTask._aws_session_for_task_arn`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AwsQuantumTask._aws_session_for_task_arn`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [task_arn: str]. - DOC501: Function `_create_annealing_device_params` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/aws/aws_quantum_task_batch.py - DOC501: Method `AwsQuantumTaskBatch.results` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `AwsQuantumTaskBatch.retry_unsuccessful_tasks` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/aws/aws_session.py - DOC106: Method `AwsSession.create_quantum_task`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `AwsSession.create_quantum_task`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC106: Method `AwsSession.create_job`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `AwsSession.create_job`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC001: Function/method `parse_s3_uri`: Potential formatting errors in docstring. Error message: Expected a colon in 'a valid S3 URI.'. - DOC101: Method `AwsSession.parse_s3_uri`: Docstring contains fewer arguments than in function signature. - DOC109: Method `AwsSession.parse_s3_uri`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AwsSession.parse_s3_uri`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [s3_uri: str]. - DOC201: Method `AwsSession.parse_s3_uri` does not have a return section in docstring - DOC203: Method `AwsSession.parse_s3_uri` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). - DOC501: Method `AwsSession.parse_s3_uri` has "raise" statements, but the docstring does not have a "Raises" section - DOC001: Function/method `construct_s3_uri`: Potential formatting errors in docstring. Error message: Expected a colon in 'valid to generate an S3 URI'. - DOC101: Method `AwsSession.construct_s3_uri`: Docstring contains fewer arguments than in function signature. - DOC109: Method `AwsSession.construct_s3_uri`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AwsSession.construct_s3_uri`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [*dirs: str, bucket: str]. - DOC201: Method `AwsSession.construct_s3_uri` does not have a return section in docstring - DOC203: Method `AwsSession.construct_s3_uri` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). - DOC501: Method `AwsSession.construct_s3_uri` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `AwsSession.get_full_image_tag` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/angled_gate.py - DOC101: Method `AngledGate.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `AngledGate.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `AngledGate.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AngledGate.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC501: Method `DoubleAngledGate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `TripleAngledGate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/braket_program_context.py - DOC101: Method `BraketProgramContext.add_gate_instruction`: Docstring contains fewer arguments than in function signature. - DOC103: Method `BraketProgramContext.add_gate_instruction`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [*params: ]. --------------------- -src/braket/circuits/circuit.py - DOC101: Method `Circuit.__init__`: Docstring contains fewer arguments than in function signature. - DOC103: Method `Circuit.__init__`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. - DOC502: Method `Circuit.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC105: Method `Circuit.apply_gate_noise`: Argument names match, but type hints do not match - DOC001: Function/method `_validate_parameters`: Potential formatting errors in docstring. Error message: No specification for "Raises": "" - DOC101: Method `Circuit._validate_parameters`: Docstring contains fewer arguments than in function signature. - DOC109: Method `Circuit._validate_parameters`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Circuit._validate_parameters`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [parameter_values: dict[str, Number]]. - DOC501: Method `Circuit._validate_parameters` has "raise" statements, but the docstring does not have a "Raises" section - DOC101: Method `Circuit.add`: Docstring contains fewer arguments than in function signature. - DOC103: Method `Circuit.add`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. - DOC502: Method `Circuit.to_unitary` has a "Raises" section in the docstring, but there are not "raise" statements in the body --------------------- -src/braket/circuits/compiler_directive.py - DOC501: Method `CompilerDirective.__init__` has "raise" statements, but the docstring does not have a "Raises" section - DOC101: Method `CompilerDirective.to_ir`: Docstring contains fewer arguments than in function signature. - DOC103: Method `CompilerDirective.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC501: Method `CompilerDirective.counterpart` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/gate.py - DOC502: Method `Gate.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC501: Method `Gate.adjoint` has "raise" statements, but the docstring does not have a "Raises" section - DOC001: Function/method `to_ir`: Potential formatting errors in docstring. Error message: Expected a colon in "properties don't correspond to the `ir_type`.". - DOC101: Method `Gate.to_ir`: Docstring contains fewer arguments than in function signature. - DOC109: Method `Gate.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Gate.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [control: Optional[QubitSet], control_state: Optional[BasisStateInput], ir_type: IRType, power: float, serialization_properties: Optional[SerializationProperties], target: QubitSet]. - DOC201: Method `Gate.to_ir` does not have a return section in docstring - DOC203: Method `Gate.to_ir` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). - DOC501: Method `Gate.to_ir` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Gate._to_jaqcd` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/gates.py - DOC105: Method `Unitary.__init__`: Argument names match, but type hints do not match - DOC105: Method `Unitary.unitary`: Argument names match, but type hints do not match - DOC501: Method `PulseGate.__init__` has "raise" statements, but the docstring does not have a "Raises" section - DOC101: Method `PulseGate.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `PulseGate.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `PulseGate.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `PulseGate.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. --------------------- -src/braket/circuits/noise.py - DOC502: Method `Noise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC001: Function/method `to_ir`: Potential formatting errors in docstring. Error message: Expected a colon in "properties don't correspond to the `ir_type`.". - DOC101: Method `Noise.to_ir`: Docstring contains fewer arguments than in function signature. - DOC109: Method `Noise.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Noise.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [ir_type: IRType, serialization_properties: SerializationProperties | None, target: QubitSet]. - DOC201: Method `Noise.to_ir` does not have a return section in docstring - DOC203: Method `Noise.to_ir` return type(s) in docstring not consistent with the return annotation. Return annotation has 1 type(s); docstring return section has 0 type(s). - DOC501: Method `Noise.to_ir` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Noise._to_jaqcd` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Noise._to_openqasm` has "raise" statements, but the docstring does not have a "Raises" section - DOC101: Method `Noise.to_matrix`: Docstring contains fewer arguments than in function signature. - DOC106: Method `Noise.to_matrix`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `Noise.to_matrix`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Noise.to_matrix`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. - DOC203: Method `Noise.to_matrix` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Iterable[np.ndarray]']; docstring return section types: ['Iterable[ndarray]'] - DOC501: Method `Noise.to_matrix` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Noise.from_dict` has "raise" statements, but the docstring does not have a "Raises" section - DOC502: Method `SingleProbabilisticNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC101: Method `SingleProbabilisticNoise.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `SingleProbabilisticNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `SingleProbabilisticNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `SingleProbabilisticNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC502: Method `SingleProbabilisticNoise_34.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC502: Method `SingleProbabilisticNoise_1516.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC101: Method `MultiQubitPauliNoise.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `MultiQubitPauliNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `MultiQubitPauliNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `MultiQubitPauliNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC203: Method `PauliNoise.probX` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] - DOC203: Method `PauliNoise.probY` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] - DOC203: Method `PauliNoise.probZ` return type(s) in docstring not consistent with the return annotation. Return annotation types: ['Union[FreeParameterExpression, float]']; docstring return section types: [''] - DOC101: Method `PauliNoise.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `PauliNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `PauliNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `PauliNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC502: Method `DampingNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC101: Method `DampingNoise.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `DampingNoise.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `DampingNoise.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `DampingNoise.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC502: Method `GeneralizedAmplitudeDampingNoise.__init__` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC501: Function `_validate_param_value` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/noise_helpers.py - DOC501: Function `check_noise_target_gates` has "raise" statements, but the docstring does not have a "Raises" section - DOC105: Function `check_noise_target_unitary`: Argument names match, but type hints do not match - DOC501: Function `check_noise_target_unitary` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Function `check_noise_target_qubits` has "raise" statements, but the docstring does not have a "Raises" section - DOC105: Function `apply_noise_to_gates`: Argument names match, but type hints do not match - DOC502: Function `apply_noise_to_gates` has a "Raises" section in the docstring, but there are not "raise" statements in the body --------------------- -src/braket/circuits/noise_model/criteria.py - DOC501: Method `Criteria.applicable_key_types` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Criteria.get_keys` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Criteria.to_dict` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Criteria.from_dict` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/noise_model/criteria_input_parsing.py - DOC501: Function `parse_operator_input` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Function `parse_qubit_input` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/noise_model/initialization_criteria.py - DOC501: Method `InitializationCriteria.qubit_intersection` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/noise_model/result_type_criteria.py - DOC501: Method `ResultTypeCriteria.result_type_matches` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/noises.py - DOC101: Method `PauliChannel.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `PauliChannel.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `PauliChannel.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `PauliChannel.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `Depolarizing.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `Depolarizing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `Depolarizing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Depolarizing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `TwoQubitDepolarizing.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `TwoQubitDepolarizing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `TwoQubitDepolarizing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `TwoQubitDepolarizing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `TwoQubitDephasing.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `TwoQubitDephasing.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `TwoQubitDephasing.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `TwoQubitDephasing.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `TwoQubitPauliChannel.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `TwoQubitPauliChannel.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `TwoQubitPauliChannel.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `TwoQubitPauliChannel.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `AmplitudeDamping.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `AmplitudeDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `AmplitudeDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `AmplitudeDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `GeneralizedAmplitudeDamping.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `GeneralizedAmplitudeDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `GeneralizedAmplitudeDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `GeneralizedAmplitudeDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC101: Method `PhaseDamping.bind_values`: Docstring contains fewer arguments than in function signature. - DOC106: Method `PhaseDamping.bind_values`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `PhaseDamping.bind_values`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `PhaseDamping.bind_values`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. - DOC501: Method `Kraus.kraus` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Kraus.to_dict` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Kraus.from_dict` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/observable.py - DOC501: Method `Observable._to_openqasm` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Observable.basis_rotation_gates` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Observable.eigenvalues` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `Observable.eigenvalue` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/observables.py - DOC501: Method `TensorProduct.__init__` has "raise" statements, but the docstring does not have a "Raises" section - DOC501: Method `TensorProduct.eigenvalue` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/circuits/operator.py - DOC101: Method `Operator.to_ir`: Docstring contains fewer arguments than in function signature. - DOC106: Method `Operator.to_ir`: The option `--arg-type-hints-in-signature` is `True` but there are no argument type hints in the signature - DOC109: Method `Operator.to_ir`: The option `--arg-type-hints-in-docstring` is `True` but there are no type hints in the docstring arg list - DOC103: Method `Operator.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. --------------------- -src/braket/circuits/result_type.py - DOC101: Method `ResultType.to_ir`: Docstring contains fewer arguments than in function signature. - DOC103: Method `ResultType.to_ir`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: ]. --------------------- -src/braket/devices/local_simulator.py - DOC101: Method `LocalSimulator.run_batch`: Docstring contains fewer arguments than in function signature. - DOC103: Method `LocalSimulator.run_batch`: Docstring arguments are different from function arguments. (Or could be other formatting issues: https://jsh9.github.io/pydoclint/violation_codes.html#notes-on-doc103 ). Arguments in the function signature but not in the docstring: [**kwargs: , *args: ]. - DOC501: Method `LocalSimulator.run_batch` has "raise" statements, but the docstring does not have a "Raises" section --------------------- -src/braket/tasks/gate_model_quantum_task_result.py - DOC502: Method `GateModelQuantumTaskResult.from_object` has a "Raises" section in the docstring, but there are not "raise" statements in the body - DOC502: Method `GateModelQuantumTaskResult.from_string` has a "Raises" section in the docstring, but there are not "raise" statements in the body --------------------- diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index 0428abe20..4f6dda34e 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -68,7 +68,7 @@ def from_ir(source: ir.Program) -> AnalogHamiltonianSimulation: """ atom_arrangement = AtomArrangement() for site, fill in zip( - source.setup.ahs_register.sites, source.setup.ahs_register.filling, strict=False + source.setup.ahs_register.sites, source.setup.ahs_register.filling, strict=True ): atom_arrangement.add( coordinate=site, site_type=SiteType.FILLED if fill == 1 else SiteType.VACANT diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index a07b5d3d7..13f2796f2 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -173,7 +173,7 @@ def from_lists( phase = TimeSeries() for t, amplitude_value, detuning_value, phase_value in zip( - times, amplitudes, detunings, phases, strict=False + times, amplitudes, detunings, phases, strict=True ): amplitude.put(t, amplitude_value) detuning.put(t, detuning_value) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 80730b4c5..51fbcdd28 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -79,7 +79,7 @@ def from_lists(times: list[float], values: list[float], pattern: list[float]) -> raise ValueError("The length of the times and values lists must be equal.") magnitude = TimeSeries() - for t, v in zip(times, values, strict=False): + for t, v in zip(times, values, strict=True): magnitude.put(t, v) return LocalDetuning(Field(magnitude, Pattern(pattern))) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 792b01a5a..2c8a81692 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -166,7 +166,7 @@ def _tasks_inputs_gatedefs( batch_length = 1 arg_lengths = [] - for arg, single_arg_type in zip(args, single_arg_types, strict=False): + for arg, single_arg_type in zip(args, single_arg_types, strict=True): arg_length = 1 if isinstance(arg, single_arg_type) else len(arg) arg_lengths.append(arg_length) @@ -182,7 +182,7 @@ def _tasks_inputs_gatedefs( if isinstance(args[i], dict | single_task_type): args[i] = repeat(args[i], batch_length) - tasks_inputs_definitions = list(zip(*args, strict=False)) + tasks_inputs_definitions = list(zip(*args, strict=True)) for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): @@ -320,7 +320,7 @@ def results( self._results = AwsQuantumTaskBatch._retrieve_results(self._tasks, self._max_workers) self._unsuccessful = { task.id - for task, result in zip(self._tasks, self._results, strict=False) + for task, result in zip(self._tasks, self._results, strict=True) if not result } @@ -371,15 +371,15 @@ def retry_unsuccessful_tasks(self) -> bool: *self._aws_quantum_task_args, **self._aws_quantum_task_kwargs, ) - for index, task in zip(unsuccessful_indices, retried_tasks, strict=False): + for index, task in zip(unsuccessful_indices, retried_tasks, strict=True): self._tasks[index] = task retried_results = AwsQuantumTaskBatch._retrieve_results(retried_tasks, self._max_workers) - for index, result in zip(unsuccessful_indices, retried_results, strict=False): + for index, result in zip(unsuccessful_indices, retried_results, strict=True): self._results[index] = result self._unsuccessful = { task.id - for task, result in zip(retried_tasks, retried_results, strict=False) + for task, result in zip(retried_tasks, retried_results, strict=True) if not result } return not self._unsuccessful diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b828bc80e..aa26e7149 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -636,7 +636,7 @@ def add_circuit( if target is not None: keys = sorted(circuit.qubits) values = target - target_mapping = dict(zip(keys, values, strict=False)) + target_mapping = dict(zip(keys, values, strict=True)) for instruction in circuit.instructions: self.add_instruction(instruction, target_mapping=target_mapping) @@ -702,7 +702,7 @@ def add_verbatim_box( if target is not None: keys = sorted(verbatim_circuit.qubits) values = target - target_mapping = dict(zip(keys, values, strict=False)) + target_mapping = dict(zip(keys, values, strict=True)) if verbatim_circuit.result_types: raise ValueError("Verbatim subcircuit is not measured and cannot have result types") @@ -1548,7 +1548,7 @@ def _add_fixed_argument_calibrations( ) additional_calibrations[bound_key] = calibration(**{ p.name if isinstance(p, FreeParameterExpression) else p: v - for p, v in zip(gate.parameters, instruction.operator.parameters, strict=False) + for p, v in zip(gate.parameters, instruction.operator.parameters, strict=True) }) return additional_calibrations diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index c05801fe9..2ed8646cb 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -571,7 +571,7 @@ def _to_openqasm( raise ValueError( f"Invalid target of length {len(target)} for Sum with {len(self.summands)} terms" ) - for i, (term, term_target) in enumerate(zip(self.summands, target, strict=False)): + for i, (term, term_target) in enumerate(zip(self.summands, target, strict=True)): if term.qubit_count != len(term_target): raise ValueError( f"Invalid target for term {i} of Sum. " @@ -583,7 +583,7 @@ def _to_openqasm( ir_type=IRType.OPENQASM, serialization_properties=serialization_properties, ) - for obs, term_target in zip(self.summands, target, strict=False) + for obs, term_target in zip(self.summands, target, strict=True) ).replace("+ -", "- ") @property diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index a59383c71..1a7b48e35 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -216,7 +216,7 @@ def __init__( "target length is equal to the observable term's qubits count." ) self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(self._target, observable.summands, strict=False): + for term_target, obs in zip(self._target, observable.summands, strict=True): if obs.qubit_count != len(term_target): raise ValueError( "Sum observable's target shape must be a nested list where each term's " diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index e8e06dd44..04bd7f059 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -124,7 +124,7 @@ def _create_diagram_column( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = dict(zip(control_qubits, control_state, strict=False)) + map_control_qubit_states = dict(zip(control_qubits, control_state, strict=True)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 6dff139b4..4b49bf90a 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -164,7 +164,7 @@ def _build_parameters( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = dict(zip(control_qubits, control_state, strict=False)) + map_control_qubit_states = dict(zip(control_qubits, control_state, strict=True)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) diff --git a/src/braket/program_sets/parameter_sets.py b/src/braket/program_sets/parameter_sets.py index 52726d763..89c3063f3 100644 --- a/src/braket/program_sets/parameter_sets.py +++ b/src/braket/program_sets/parameter_sets.py @@ -60,7 +60,7 @@ def __init__( else: raise TypeError(f"Unsupported type {type(parameter_sets)} for ParameterSets") elif keys: - self._inputs = ParameterSets._mapping_to_dict(dict(_strict_zip(keys, values))) + self._inputs = ParameterSets._mapping_to_dict(dict(zip(keys, values, strict=True))) elif kwargs: self._inputs = ParameterSets._mapping_to_dict(kwargs) else: @@ -96,7 +96,7 @@ def __add__(self, other: ParameterSetsLike): def __mul__(self, other: ParameterSetsLike): other_cast = other if isinstance(other, ParameterSets) else ParameterSets(other) product = { - k: list(sum(_strict_zip(*([v] * len(other_cast))), ())) + k: list(sum(zip(*([v] * len(other_cast)), strict=True), ())) for k, v in self.as_dict().items() } other_multiplied = {k: v * len(self) for k, v in (other_cast.as_dict() or {}).items()} @@ -156,12 +156,3 @@ def _validate_combinations( raise ValueError("Can only populate one of kwargs or key-value pairs") if (keys is not None and values is None) or (values is not None and keys is None): raise ValueError("Both keys and values must be specified") - - -def _strict_zip(*args) -> zip: - # TODO: Remove and replace all usage with zip(..., strict=True) once we drop Python 3.9 support - it = iter(args) - length = len(next(it)) - if not all(len(lst) == length for lst in it): - raise ValueError("Lists must be of equal length") - return zip(*args, strict=False) diff --git a/src/braket/program_sets/program_set.py b/src/braket/program_sets/program_set.py index 4b79679ef..0f4964af5 100644 --- a/src/braket/program_sets/program_set.py +++ b/src/braket/program_sets/program_set.py @@ -21,7 +21,6 @@ from braket.circuits.observables import Sum from braket.circuits.serialization import IRType from braket.program_sets.circuit_binding import CircuitBinding -from braket.program_sets.parameter_sets import _strict_zip from braket.pulse import PulseSequence from braket.registers import QubitSet @@ -226,7 +225,9 @@ def _zip_circuit_bindings( return ProgramSet( [ CircuitBinding(circuit, [input_set], [observable]) - for input_set, observable in _strict_zip(input_sets, circuit_binding.observables) + for input_set, observable in zip( + input_sets, circuit_binding.observables, strict=True + ) ], shots_per_executable, ) @@ -242,7 +243,7 @@ def _zip_circuit_bindings( return ProgramSet( [ CircuitBinding(circuit, [input_set], [observable]) - for input_set, observable in _strict_zip(inputs_list, observables) + for input_set, observable in zip(inputs_list, observables, strict=True) ], shots_per_executable, ) @@ -262,7 +263,9 @@ def _zip_circuits( return ProgramSet( [ CircuitBinding(circuit, [input_set], [observable]) - for circuit, input_set, observable in _strict_zip(circuits, input_sets, observables) + for circuit, input_set, observable in zip( + circuits, input_sets, observables, strict=True + ) ], shots_per_executable, ) @@ -272,7 +275,7 @@ def _zip_circuits( return ProgramSet( [ CircuitBinding(circuit, [input_set], None) - for circuit, input_set in _strict_zip(circuits, input_sets) + for circuit, input_set in zip(circuits, input_sets, strict=True) ], shots_per_executable, ) @@ -282,7 +285,7 @@ def _zip_circuits( return ProgramSet( [ CircuitBinding(circuit, None, [observable]) - for circuit, observable in _strict_zip(circuits, observables) + for circuit, observable in zip(circuits, observables, strict=True) ], shots_per_executable, ) diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index 4fdf6fc31..9f4be97ca 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -126,7 +126,7 @@ def get_counts(self) -> dict[str, int]: # converting presequence and postsequence measurements to state_idx state_idx = [ 0 if pre_i == 0 else 1 if post_i == 0 else 2 - for pre_i, post_i in zip(pre, post, strict=False) + for pre_i, post_i in zip(pre, post, strict=True) ] state = "".join(states[s_idx] for s_idx in state_idx) state_counts.update([state]) diff --git a/src/braket/tasks/program_set_quantum_task_result.py b/src/braket/tasks/program_set_quantum_task_result.py index 330655f0b..639e7afc5 100644 --- a/src/braket/tasks/program_set_quantum_task_result.py +++ b/src/braket/tasks/program_set_quantum_task_result.py @@ -36,7 +36,6 @@ from braket.circuits.observable import EULER_OBSERVABLE_PREFIX from braket.circuits.observables import Sum from braket.program_sets import CircuitBinding, ParameterSets, ProgramSet -from braket.program_sets.parameter_sets import _strict_zip from braket.tasks.measurement_utils import ( expectation_from_measurements, measurement_counts_from_measurements, @@ -409,7 +408,7 @@ def _get_entries( ) -> list[CompositeEntry | MeasuredEntry]: if program_set: entries = [] - for entry, result in _strict_zip(program_set.entries, program_results): + for entry, result in zip(program_set.entries, program_results, strict=True): entries.append( # The program has observables available to compute ProgramSetQuantumTaskResult._result_to_entry( diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 9b442218b..15a07c8db 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -115,7 +115,7 @@ def from_lists(times: list[float], values: list[float]) -> TimeSeries: ) ts = TimeSeries() - for t, v in zip(times, values, strict=False): + for t, v in zip(times, values, strict=True): ts.put(t, v) return ts @@ -181,7 +181,7 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: new_time_series = TimeSeries() new_times = self.times() + other.times() new_values = self.values() + other.values() - for t, v in zip(new_times, new_values, strict=False): + for t, v in zip(new_times, new_values, strict=True): new_time_series.put(t, v) return new_time_series @@ -261,7 +261,7 @@ def stitch( ) new_values = [*self.values()[:-1], bndry_val, *other.values()[1:]] - for t, v in zip(new_times, new_values, strict=False): + for t, v in zip(new_times, new_values, strict=True): new_time_series.put(t, v) return new_time_series diff --git a/test/unit_tests/braket/program_sets/test_parameter_sets.py b/test/unit_tests/braket/program_sets/test_parameter_sets.py index 6663af2b7..8b842d2b4 100644 --- a/test/unit_tests/braket/program_sets/test_parameter_sets.py +++ b/test/unit_tests/braket/program_sets/test_parameter_sets.py @@ -14,7 +14,7 @@ import numpy as np import pytest -from braket.program_sets.parameter_sets import ParameterSets, _strict_zip +from braket.program_sets.parameter_sets import ParameterSets def test_dict(): @@ -155,9 +155,3 @@ def test_kwargs_mismatched(): def test_add_mismatch(): with pytest.raises(ValueError): ParameterSets({"theta": [1, 2, 3], "phi": [4, 5, 6]}) + [{"theta": 3}, {"theta": 10}] - - -@pytest.mark.parametrize("lists", [[[1, 2, 2], [2, 3, 4], [1, 2]], [[1, 2], [1, 2, 3]]]) -def test_strict_zip_mismatch(lists): - with pytest.raises(ValueError): - _strict_zip(*lists) From f377cd13aa7d4ef85d161bca839b3d15278a5b33 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Thu, 21 Aug 2025 01:44:49 -0400 Subject: [PATCH 318/347] fix: validation on measured qubits (#1109) --- src/braket/circuits/circuit.py | 25 +++++++----- .../braket/circuits/test_circuit.py | 40 +++++++++++++++++-- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index aa26e7149..f1d35ab7c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -458,17 +458,20 @@ def _check_if_qubit_measured( Raises: ValueError: If adding a gate or noise operation after a measure instruction. """ - if self._measure_targets: - measure_on_target_mapping = target_mapping and any( - targ in self._measure_targets for targ in target_mapping.values() - ) - if ( - # check if there is a measure instruction on the targeted qubit(s) - measure_on_target_mapping - or any(tar in self._measure_targets for tar in QubitSet(target)) - or any(tar in self._measure_targets for tar in QubitSet(instruction.target)) - ): - raise ValueError("cannot apply instruction to measured qubits.") + if not self._measure_targets: + return + + if target: + mapped_target_qubits = QubitSet(target) + elif target_mapping: + mapped_target_qubits = QubitSet([ + target_mapping[qubit] for qubit in QubitSet(instruction.target) + ]) + else: # both `target` and `target_mapping` is None + mapped_target_qubits = QubitSet(instruction.target) + + if any(qubit in self._measure_targets for qubit in mapped_target_qubits): + raise ValueError("cannot apply instruction to measured qubits.") def add_instruction( self, diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 4528b294c..062d4ecbc 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -778,21 +778,53 @@ def test_measure_with_readout_noise(): def test_measure_gate_after_with_target_mapping(): - # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + circuit = ( + Circuit() + .h(0) + .cnot(0, 1) + .measure([0, 1]) + .add_instruction(instr, target_mapping={0: 10, 1: 11}) + ) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + ) + assert circuit == expected + + +def test_measure_gate_after_with_target_mapping_invalid(): message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): - Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction( + Circuit().h(10).cnot(10, 11).measure([10, 11]).add_instruction( instr, target_mapping={0: 10, 1: 11} ) def test_measure_gate_after_with_target(): - # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + circuit = Circuit().h(0).cnot(0, 1).measure([0, 1]).add_instruction(instr, target=[10, 11]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Gate.CNot(), [10, 11])) + ) + assert circuit == expected + + +def test_measure_gate_after_with_target_invalid(): message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): - Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction(instr, target=[10, 11]) + Circuit().h(10).cnot(10, 11).measure([10, 11]).add_instruction(instr, target=[10, 11]) def test_measure_gate_after_measurement(): From f66f36ecbc397893b26ed4780baf64e860cbb097 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Aug 2025 16:19:48 +0000 Subject: [PATCH 319/347] prepare release v1.99.0 --- CHANGELOG.md | 15 +++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4c49e90a..a50a2e5be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## v1.99.0 (2025-08-21) + +### Deprecations and Removals + + * Drop Python 3.9, support 3.12, 3.13 + +### Bug Fixes and Other Changes + + * validation on measured qubits + * Make `zip` strict + +### Documentation Changes + + * Use latest for doc generation + ## v1.98.0 (2025-08-20) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1e3ccb65c..f697cb2c1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.98.1.dev0" +__version__ = "1.99.0" From 041c4e54081ddc1bf7d147ec58bed8306a92c6dd Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Aug 2025 16:19:48 +0000 Subject: [PATCH 320/347] update development version to v1.99.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f697cb2c1..0fda46627 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.99.0" +__version__ = "1.99.1.dev0" From fa6f520d8e323a3432b2f61f145b0bb52c3117f4 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Thu, 21 Aug 2025 12:05:33 -0700 Subject: [PATCH 321/347] fix: resolve failing tests on py312 on windows (#1113) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- .github/workflows/python-package.yml | 4 ---- test/unit_tests/braket/aws/test_aws_session.py | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d72d70269..79b619974 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -23,10 +23,6 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.10", "3.11", "3.12", "3.13"] - exclude: - # TODO: Fix test_aws_session.py::test_upload_local_data_absolute - - os: windows-latest - python-version: 3.12 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index a7cb4aa0d..15172d796 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -999,7 +999,7 @@ def test_account_id_idempotency(aws_session, account_id): def test_upload_local_data(aws_session): - with tempfile.TemporaryDirectory() as temp_dir: + with tempfile.TemporaryDirectory(dir=os.getcwd()) as temp_dir: os.chdir(temp_dir) Path("input-dir", "pref-dir", "sub-pref-dir").mkdir(parents=True) @@ -1031,7 +1031,7 @@ def test_upload_local_data(aws_session): def test_upload_local_data_absolute(aws_session): - with tempfile.TemporaryDirectory() as temp_dir: + with tempfile.TemporaryDirectory(dir=os.getcwd()) as temp_dir: Path(temp_dir, "input-dir", "pref-dir", "sub-pref-dir").mkdir(parents=True) Path(temp_dir, "input-dir", "not-pref-dir").mkdir() From ea5d05c6f53d1bb0f9c266230b84a75ea3dc4816 Mon Sep 17 00:00:00 2001 From: robotastray Date: Thu, 21 Aug 2025 23:22:33 +0100 Subject: [PATCH 322/347] feature: `add_verbatim_marker()` method to `BraketProgramContext` class for updated `Circuit.from_ir` (#1085) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Co-authored-by: Cody Wang --- src/braket/circuits/braket_program_context.py | 8 ++- src/braket/circuits/translations.py | 7 +++ .../braket/circuits/test_circuit.py | 49 +++++++++++++++++++ .../iqm/test_iqm_experimental_capabilities.py | 15 +++--- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 9159a6ad2..20e67d16c 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -14,6 +14,7 @@ from collections.abc import Iterable import numpy as np +from braket.default_simulator.openqasm.interpreter import VerbatimBoxDelimiter from braket.default_simulator.openqasm.program_context import AbstractProgramContext from braket.ir.jaqcd.program_v1 import Results from sympy import Expr, Number @@ -24,6 +25,7 @@ from braket.circuits.noises import Kraus from braket.circuits.translations import ( BRAKET_GATES, + COMPILER_DIRECTIVES, braket_result_to_result_type, one_prob_noise_map, ) @@ -65,8 +67,7 @@ def add_phase_instruction(self, target: tuple[int], phase_value: float) -> None: target (tuple[int]): Unused phase_value (float): The phase value to be applied """ - instruction = Instruction(BRAKET_GATES["gphase"](phase_value)) - self._circuit.add_instruction(instruction) + self._circuit.gphase(phase_value) def add_gate_instruction( self, gate_name: str, target: tuple[int], *params, ctrl_modifiers: list[int], power: float @@ -173,3 +174,6 @@ def add_measure( index = classical_targets[iter] if classical_targets else iter instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) + + def add_verbatim_marker(self, marker: VerbatimBoxDelimiter) -> None: + self._circuit.add_instruction(Instruction(COMPILER_DIRECTIVES[marker](), target=[])) diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 726b1c3a9..f50a1e206 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -18,6 +18,7 @@ from typing import NoReturn import braket.ir.jaqcd.shared_models as models +from braket.default_simulator.openqasm.interpreter import VerbatimBoxDelimiter from braket.ir.jaqcd import ( Amplitude, DensityMatrix, @@ -32,6 +33,7 @@ import braket.circuits.gates as braket_gates import braket.circuits.result_types as ResultTypes # noqa: N812 from braket.circuits import Observable, noises, observables +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.experimental_capabilities.iqm.classical_control import CCPRx, MeasureFF BRAKET_GATES = { @@ -79,6 +81,11 @@ "measure_ff": MeasureFF, } +COMPILER_DIRECTIVES = { + VerbatimBoxDelimiter.START_VERBATIM: StartVerbatimBox, + VerbatimBoxDelimiter.END_VERBATIM: EndVerbatimBox, +} + one_prob_noise_map = { "bit_flip": noises.BitFlip, "phase_flip": noises.PhaseFlip, diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 062d4ecbc..ee432a3c5 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -910,6 +910,55 @@ def test_from_ir_round_trip_transformation(): assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") +def test_from_ir_with_verbatim_box(): + ir = OpenQasmProgram( + source="\n".join([ + "OPENQASM 3.0;", + "#pragma braket verbatim", + "box {", + " h $0;", + " cnot $0, $1;", + "}", + ]), + inputs={}, + ) + + verbatim_subcirc = Circuit().h(0).cnot(0, 1) + expected_circ = Circuit().add_verbatim_box(verbatim_subcirc) + actual_circ = Circuit().from_ir(source=ir.source, inputs=ir.inputs) + assert actual_circ == expected_circ + + +def test_from_ir_with_mixed_verbatim_non_verbatim_instr(): + ir = OpenQasmProgram( + source="\n".join([ + "OPENQASM 3.0;", + "qubit[2] q;", + "bit[2] c;", + # Non-verbatim instructions + "h q[0];", + "cnot q[0], q[1];", + # Verbatim block + "#pragma braket verbatim", + "box {", + " h $0;", + " cnot $0, $1;", + "}", + "c[0] = measure $0;", + "c[1] = measure $1;", + ]), + inputs={}, + ) + + verbatim_subcirc = Circuit().h(0).cnot(0, 1) + expected_circ = Circuit().h(0).cnot(0, 1) + expected_circ.add_verbatim_box(verbatim_subcirc) + expected_circ.measure(0) + expected_circ.measure(1) + actual_circ = Circuit().from_ir(source=ir.source, inputs=ir.inputs) + assert actual_circ == expected_circ + + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) diff --git a/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py index a2880fa2b..60fb7a33c 100644 --- a/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py +++ b/test/unit_tests/braket/experimental_capabilities/iqm/test_iqm_experimental_capabilities.py @@ -186,19 +186,22 @@ def test_measureff_ccprx_from_ir(): """ ir = OpenQasmProgram(source=openqasm_source) circuit_from_ir = Circuit.from_ir(ir) + assert len(circuit_from_ir.instructions) == 5 - assert len(circuit_from_ir.instructions) == 3 + assert circuit_from_ir.instructions[0].operator.name == "StartVerbatimBox" - assert circuit_from_ir.instructions[0].operator.name == "PRx" - assert isinstance(circuit_from_ir.instructions[1].operator, MeasureFF) - assert isinstance(circuit_from_ir.instructions[2].operator, CCPRx) + assert circuit_from_ir.instructions[1].operator.name == "PRx" + assert isinstance(circuit_from_ir.instructions[2].operator, MeasureFF) + assert isinstance(circuit_from_ir.instructions[3].operator, CCPRx) - instruction = circuit_from_ir.instructions[1] + assert circuit_from_ir.instructions[4].operator.name == "EndVerbatimBox" + + instruction = circuit_from_ir.instructions[2] params = instruction.operator.parameters assert params[0] == 0 assert instruction.target == [1] - instruction = circuit_from_ir.instructions[2] + instruction = circuit_from_ir.instructions[3] params = instruction.operator.parameters assert np.isclose(params[0], 3.141592653589793) assert np.isclose(params[1], 0) From 7f9965ecf5e7802d811c6a43821b5531c1863b90 Mon Sep 17 00:00:00 2001 From: "Tim (Yi-Ting)" Date: Fri, 22 Aug 2025 13:27:49 -0400 Subject: [PATCH 323/347] fix: caching measured qubits (#1114) --- src/braket/circuits/circuit.py | 30 ++++++++++++------- .../braket/circuits/test_circuit.py | 13 ++++++++ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index f1d35ab7c..9268abad7 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -435,6 +435,20 @@ def _add_to_qubit_observable_set(self, result_type: ResultType) -> None: if isinstance(result_type, ObservableResultType) and result_type.target: self._qubit_observable_set.update(result_type.target) + def _map_target_qubits( + self, + source_qubits: QubitSetInput, + target: QubitSetInput | None = None, + target_mapping: dict[QubitInput, QubitInput] | None = None, + ) -> QubitSet: + if target: + mapped_target_qubits = target + elif target_mapping: + mapped_target_qubits = [target_mapping[qubit] for qubit in source_qubits] + else: # both `target` and `target_mapping` is None + mapped_target_qubits = source_qubits + return QubitSet(mapped_target_qubits) + def _check_if_qubit_measured( self, instruction: Instruction, @@ -461,16 +475,10 @@ def _check_if_qubit_measured( if not self._measure_targets: return - if target: - mapped_target_qubits = QubitSet(target) - elif target_mapping: - mapped_target_qubits = QubitSet([ - target_mapping[qubit] for qubit in QubitSet(instruction.target) - ]) - else: # both `target` and `target_mapping` is None - mapped_target_qubits = QubitSet(instruction.target) - - if any(qubit in self._measure_targets for qubit in mapped_target_qubits): + if any( + qubit in self._measure_targets + for qubit in self._map_target_qubits(instruction.target, target, target_mapping) + ): raise ValueError("cannot apply instruction to measured qubits.") def add_instruction( @@ -530,7 +538,7 @@ def add_instruction( # Update measure targets if instruction is a measurement if isinstance(instruction.operator, Measure): - measure_target = target or instruction.target[0] + measure_target = self._map_target_qubits(instruction.target, target, target_mapping)[0] self._measure_targets = (self._measure_targets or []) + [measure_target] if not target_mapping and not target: diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index ee432a3c5..a76794bec 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -840,6 +840,19 @@ def test_measure_gate_after_measurement(): assert circ == expected +def test_measure_add_circuit_target_mapping(): + circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + circuit = Circuit().add_circuit(circuit, target_mapping={0: 1, 1: 0}) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 1)) + .add_instruction(Instruction(Gate.CNot(), [1, 0])) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circuit == expected + + def test_to_ir_with_measure(): circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 2]) expected_ir = OpenQasmProgram( From 53446de64082eda0e8732ad7c70ab96d41339502 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 22 Aug 2025 18:09:00 +0000 Subject: [PATCH 324/347] prepare release v1.100.0 --- CHANGELOG.md | 11 +++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a50a2e5be..85311ff57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.100.0 (2025-08-22) + +### Features + + * `add_verbatim_marker()` method to `BraketProgramContext` class for updated `Circuit.from_ir` + +### Bug Fixes and Other Changes + + * caching measured qubits + * resolve failing tests on py312 on windows + ## v1.99.0 (2025-08-21) ### Deprecations and Removals diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0fda46627..0cc452a01 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.99.1.dev0" +__version__ = "1.100.0" From 19164922286f1395cbd0cbd7d15f5b5af0ea2a89 Mon Sep 17 00:00:00 2001 From: ci Date: Fri, 22 Aug 2025 18:09:00 +0000 Subject: [PATCH 325/347] update development version to v1.100.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 0cc452a01..c76307e77 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.100.0" +__version__ = "1.100.1.dev0" From 7cae6a71d6dd23fdd7ba2df4424c1d20b007f94b Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:11:54 -0400 Subject: [PATCH 326/347] fix: update local sim version dependency (#1116) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b3927de6c..a028d00f6 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.25.0", - "amazon-braket-default-simulator>=1.27.0", + "amazon-braket-default-simulator>=1.29.0", "oqpy~=0.3.7", "backoff", "boltons", From cde767f5623ea6d2c3186fbedfcd2eca1482a490 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Aug 2025 15:26:24 +0000 Subject: [PATCH 327/347] prepare release v1.100.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85311ff57..51f8bffee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.100.1 (2025-08-25) + +### Bug Fixes and Other Changes + + * update local sim version dependency + ## v1.100.0 (2025-08-22) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c76307e77..c31a302fa 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.100.1.dev0" +__version__ = "1.100.1" From 789588f73b3f205b559d8e4be76f80783e0ff992 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Aug 2025 15:26:24 +0000 Subject: [PATCH 328/347] update development version to v1.100.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c31a302fa..e68b69c6a 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.100.1" +__version__ = "1.100.2.dev0" From 634d66a2c11cf952b490c0e5a8368241d3fddd45 Mon Sep 17 00:00:00 2001 From: Altanali Date: Mon, 25 Aug 2025 14:36:02 -0700 Subject: [PATCH 329/347] feature: Emulators with circuit validation and noise models (#1017) Co-authored-by: Nagji Co-authored-by: Cody Wang Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Co-authored-by: maolinml <86260930+maolinml@users.noreply.github.com> --- src/braket/aws/aws_device.py | 28 +- src/braket/emulation/__init__.py | 15 + src/braket/emulation/_standardization.py | 97 ++++ .../emulation/device_emulator_properties.py | 240 +++++++++ src/braket/emulation/emulator.py | 180 +++++++ src/braket/emulation/local_emulator.py | 227 ++++++++ src/braket/emulation/pass_manager.py | 70 +++ src/braket/emulation/passes/__init__.py | 14 + .../passes/_device_emulator_validators.py | 65 +++ .../passes/circuit_passes/__init__.py | 29 ++ .../circuit_passes/connectivity_validator.py | 174 +++++++ .../gate_connectivity_validator.py | 177 +++++++ .../passes/circuit_passes/gate_validator.py | 97 ++++ .../not_implemented_validator.py | 54 ++ .../circuit_passes/qubit_count_validator.py | 50 ++ .../circuit_passes/result_type_validator.py | 94 ++++ .../emulation/passes/validation_pass.py | 49 ++ test/unit_tests/braket/aws/test_aws_device.py | 213 ++++++++ .../braket/devices/test_local_simulator.py | 34 +- test/unit_tests/braket/emulation/conftest.py | 488 ++++++++++++++++++ .../passes/test_connectivity_validator.py | 210 ++++++++ .../test_gate_connectivity_validator.py | 267 ++++++++++ .../emulation/passes/test_gate_validator.py | 157 ++++++ .../passes/test_not_implemented_validator.py | 46 ++ .../passes/test_qubit_count_validator.py | 47 ++ .../passes/test_result_type_validator.py | 102 ++++ .../braket/emulation/test__standardization.py | 35 ++ .../test_device_emulator_properties.py | 179 +++++++ .../braket/emulation/test_emulator.py | 113 ++++ .../braket/emulation/test_local_emulator.py | 96 ++++ 30 files changed, 3632 insertions(+), 15 deletions(-) create mode 100644 src/braket/emulation/__init__.py create mode 100644 src/braket/emulation/_standardization.py create mode 100644 src/braket/emulation/device_emulator_properties.py create mode 100644 src/braket/emulation/emulator.py create mode 100644 src/braket/emulation/local_emulator.py create mode 100644 src/braket/emulation/pass_manager.py create mode 100644 src/braket/emulation/passes/__init__.py create mode 100644 src/braket/emulation/passes/_device_emulator_validators.py create mode 100644 src/braket/emulation/passes/circuit_passes/__init__.py create mode 100644 src/braket/emulation/passes/circuit_passes/connectivity_validator.py create mode 100644 src/braket/emulation/passes/circuit_passes/gate_connectivity_validator.py create mode 100644 src/braket/emulation/passes/circuit_passes/gate_validator.py create mode 100644 src/braket/emulation/passes/circuit_passes/not_implemented_validator.py create mode 100644 src/braket/emulation/passes/circuit_passes/qubit_count_validator.py create mode 100644 src/braket/emulation/passes/circuit_passes/result_type_validator.py create mode 100644 src/braket/emulation/passes/validation_pass.py create mode 100644 test/unit_tests/braket/emulation/conftest.py create mode 100644 test/unit_tests/braket/emulation/passes/test_connectivity_validator.py create mode 100644 test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py create mode 100644 test/unit_tests/braket/emulation/passes/test_gate_validator.py create mode 100644 test/unit_tests/braket/emulation/passes/test_not_implemented_validator.py create mode 100644 test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py create mode 100644 test/unit_tests/braket/emulation/passes/test_result_type_validator.py create mode 100644 test/unit_tests/braket/emulation/test__standardization.py create mode 100644 test/unit_tests/braket/emulation/test_device_emulator_properties.py create mode 100644 test/unit_tests/braket/emulation/test_emulator.py create mode 100644 test/unit_tests/braket/emulation/test_local_emulator.py diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 329af760d..3ca7f181a 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -41,6 +41,8 @@ from braket.circuits.gate_calibrations import GateCalibrations from braket.circuits.noise_model import NoiseModel from braket.devices.device import Device +from braket.emulation.emulator import Emulator +from braket.emulation.local_emulator import LocalEmulator from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import _is_float from braket.program_sets import ProgramSet @@ -56,7 +58,7 @@ class AwsDeviceType(str, Enum): QPU = "QPU" -class AwsDevice(Device): +class AwsDevice(Device): # noqa: PLR0904 """Amazon Braket implementation of a device. Use this class to retrieve the latest metadata about the device and to run a quantum task on the device. @@ -118,6 +120,7 @@ def __init__( if noise_model: self._validate_device_noise_model_support(noise_model) self._noise_model = noise_model + self._emulator = None def run( self, @@ -567,6 +570,29 @@ def ports(self) -> dict[str, Port]: self._update_pulse_properties() return self._ports or {} + def emulator(self) -> Emulator: + """ + A device emulator mimics the restrictions and noise of the AWS QPU by validating and + compiling programs before running them on a simulated backend. An emulator can be used + as a soft check that a program can run the target AwsDevice. + + Returns: + Emulator: An emulator for this device, if this is not a simulator device. Raises an + exception if an emulator is requested for a simulator device. + """ + + if self._emulator is not None: + return self._emulator + + if self._type == AwsDeviceType.SIMULATOR: + raise ValueError( + "Creating an emulator from a Braket managed simulator is not supported." + ) + + self._emulator = LocalEmulator.from_device_properties(self.properties) + + return self._emulator + @staticmethod def get_devices( arns: list[str] | None = None, diff --git a/src/braket/emulation/__init__.py b/src/braket/emulation/__init__.py new file mode 100644 index 000000000..ed6f24919 --- /dev/null +++ b/src/braket/emulation/__init__.py @@ -0,0 +1,15 @@ +# 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. + +from braket.emulation.emulator import Emulator # noqa: F401 +from braket.emulation.pass_manager import PassManager # noqa: F401 diff --git a/src/braket/emulation/_standardization.py b/src/braket/emulation/_standardization.py new file mode 100644 index 000000000..9dfc65bad --- /dev/null +++ b/src/braket/emulation/_standardization.py @@ -0,0 +1,97 @@ +# 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. + + +from braket.device_schema.ionq.ionq_device_capabilities_v1 import IonqDeviceCapabilities +from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( + CoherenceTime, + Fidelity1Q, + FidelityType, + GateFidelity2Q, + OneQubitProperties, + TwoQubitProperties, +) + +from braket.circuits.translations import BRAKET_GATES + + +def _standardize_ionq_device_properties( + device_properties: IonqDeviceCapabilities, +) -> IonqDeviceCapabilities: + if ( + device_properties.braketSchemaHeader.name + != "braket.device_schema.ionq.ionq_device_capabilities" + ): + raise ValueError("The input must be of type IonqDeviceCapabilities.") + + device_properties_dict = device_properties.dict() + T1 = device_properties_dict["provider"]["timing"]["T1"] + T2 = device_properties_dict["provider"]["timing"]["T2"] + f1q = device_properties_dict["provider"]["fidelity"]["1Q"]["mean"] + f2q = device_properties_dict["provider"]["fidelity"]["2Q"]["mean"] + fspam = device_properties_dict["provider"]["fidelity"]["spam"]["mean"] + + oneQubitProperties = OneQubitProperties( + T1=CoherenceTime(value=T1, standardError=None, unit="S"), + T2=CoherenceTime(value=T2, standardError=None, unit="S"), + oneQubitFidelity=[ + Fidelity1Q( + fidelityType=FidelityType(name="RANDOMIZED_BENCHMARKING", description=None), + fidelity=f1q, + standardError=None, + ), + Fidelity1Q( + fidelityType=FidelityType(name="READOUT", description=None), + fidelity=fspam, + standardError=None, + ), + ], + ) + + two_qubit_gate_names = [ + gate + for gate in device_properties_dict["paradigm"]["nativeGateSet"] + if BRAKET_GATES[gate.lower()].fixed_qubit_count() == 2 + ] + + # Assuming there is one and only one two-qubit native gate + two_qubit_gate_name = two_qubit_gate_names[0] + + twoQubitProperties = TwoQubitProperties( + twoQubitGateFidelity=[ + GateFidelity2Q( + direction=None, + gateName=two_qubit_gate_name, + fidelity=f2q, + standardError=None, + fidelityType=FidelityType(name="RANDOMIZED_BENCHMARKING", description=None), + ) + ] + ) + + num_qubits = device_properties_dict["paradigm"]["qubitCount"] + + all_edges = [f"{i}-{j}" for i in range(num_qubits) for j in range(i + 1, num_qubits)] + device_properties.__dict__["standardized"] = { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": dict.fromkeys(range(num_qubits), oneQubitProperties), + "twoQubitProperties": dict.fromkeys(all_edges, twoQubitProperties), + } + + device_properties.paradigm.nativeGateSet = [ + gate.lower() for gate in device_properties.paradigm.nativeGateSet + ] + return device_properties diff --git a/src/braket/emulation/device_emulator_properties.py b/src/braket/emulation/device_emulator_properties.py new file mode 100644 index 000000000..155402cd0 --- /dev/null +++ b/src/braket/emulation/device_emulator_properties.py @@ -0,0 +1,240 @@ +# 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. + +import json + +from braket.device_schema.device_capabilities import DeviceCapabilities +from braket.device_schema.ionq.ionq_device_capabilities_v1 import IonqDeviceCapabilities +from braket.device_schema.result_type import ResultType +from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( + OneQubitProperties, + TwoQubitProperties, +) +from braket.schema_common.schema_base import BraketSchemaBase + +from braket.circuits.translations import BRAKET_GATES +from braket.emulation._standardization import _standardize_ionq_device_properties + + +class DeviceEmulatorProperties: + """Properties for device emulation. + + Args: + qubitCount (int): Number of qubits in the device + nativeGateSet (list[str]): List of native gates supported by the device. Must be valid + Braket gates. Valid gates include: gphase, i, h, x, y, z, cv, cnot, cy, cz, ecr, s, si, + t, ti, v, vi, phaseshift, cphaseshift, cphaseshift00, cphaseshift01, cphaseshift10, rx, + ry, rz, U, swap, iswap, pswap, xy, xx, yy, zz, ccnot, cswap, gpi, gpi2, prx, ms, unitary + connectivityGraph (dict[str, list[str]]): Graph representing qubit connectivity. If it is an + empty dictionary, the device is treated as fully connected. + oneQubitProperties (dict[str, OneQubitProperties]): Properties of one-qubit calibration + details + twoQubitProperties (dict[str, TwoQubitProperties]): Properties of two-qubit calibration + details + supportedResultTypes (list[ResultType]): List of supported result types. + """ + + def __init__( + self, + qubitCount: int, + nativeGateSet: list[str], + connectivityGraph: dict[str, list[str]], + oneQubitProperties: dict[str, OneQubitProperties], + twoQubitProperties: dict[str, TwoQubitProperties], + supportedResultTypes: list[ResultType], + ): + """Initialize a DeviceEmulatorProperties instance.""" + # Validate inputs + self._validate_native_gate_set(nativeGateSet) + self._validate_one_qubit_properties(oneQubitProperties, qubitCount) + + # Get qubit labels for further validation + indices = list(oneQubitProperties.keys()) + qubit_labels = sorted(int(x) for x in indices) + + self._validate_connectivity_graph(connectivityGraph, qubit_labels) + self._validate_two_qubit_properties(twoQubitProperties, qubit_labels) + + # Store properties + self._qubit_count = qubitCount + self._native_gate_set = nativeGateSet + self._connectivity_graph = connectivityGraph + self._one_qubit_properties = oneQubitProperties + self._two_qubit_properties = twoQubitProperties + self._supported_result_types = supportedResultTypes + + @staticmethod + def _validate_native_gate_set(nativeGateSet: list[str]) -> None: + """Validate that all gates in nativeGateSet are valid Braket gates.""" + valid_gates = ", ".join(BRAKET_GATES.keys()) + for gate in nativeGateSet: + if gate not in BRAKET_GATES: + raise ValueError( + f"Gate '{gate}' is not a valid Braket gate. Valid gates are: {valid_gates}" + ) + + @staticmethod + def _validate_one_qubit_properties( + oneQubitProperties: dict[str, OneQubitProperties], qubitCount: int + ) -> None: + """Validate oneQubitProperties.""" + if len(oneQubitProperties) != qubitCount: + raise ValueError("The length of oneQubitProperties should be the same as qubitCount") + + @staticmethod + def _node_validator(node: str, qubit_labels: list[int], field_name: str) -> None: + """Validate that a node represents a valid qubit index.""" + if int(node) not in qubit_labels: + raise ValueError( + f"Node {node} in {field_name} must represent a valid qubit index in {qubit_labels}." + ) + + @classmethod + def _validate_connectivity_graph( + cls, connectivityGraph: dict[str, list[str]], qubit_labels: list[int] + ) -> None: + """Validate connectivityGraph.""" + for node, neighbors in connectivityGraph.items(): + cls._node_validator(node, qubit_labels, "connectivityGraph") + + for neighbor in neighbors: + if int(neighbor) not in qubit_labels: + raise ValueError( + f"Neighbor {neighbor} for node {node} must represent a valid qubit index " + f"in `qubit_labels`." + ) + + @classmethod + def _validate_two_qubit_properties( + cls, twoQubitProperties: dict[str, TwoQubitProperties], qubit_labels: list[int] + ) -> None: + """Validate twoQubitProperties.""" + for edge in twoQubitProperties: + node_1, node_2 = edge.split("-") + cls._node_validator(node_1, qubit_labels, "twoQubitProperties") + cls._node_validator(node_2, qubit_labels, "twoQubitProperties") + + @property + def qubit_labels(self) -> list[int]: + """Get the sorted list of qubit indices.""" + indices = list(self.one_qubit_properties.keys()) + return sorted(int(x) for x in indices) + + @property + def fully_connected(self) -> bool: + """Determine if the connectivity graph is fully connected. + + Note: We treat the graph as undirected, and determine if it is + a complete graph by counting the number of distinct edges + """ + if not self.connectivity_graph: + return True + + edges = set() + for node, neighbors in self.connectivity_graph.items(): + edges_node = [(int(node), int(neighbor)) for neighbor in neighbors] + edges_node = [(min(edge), max(edge)) for edge in edges_node] + edges.update(edges_node) + + return len(edges) == self.qubit_count * (self.qubit_count - 1) / 2 + + @property + def directed(self) -> bool: + """Determine if the connectivity graph is a directed graph.""" + for node, neighbors in self.connectivity_graph.items(): + for neighbor in neighbors: + # If neighbor doesn't link back to node, it's directed + if node not in self.connectivity_graph.get(neighbor, []): + return True + return False + + @property + def qubit_count(self) -> int: + return self._qubit_count + + @property + def native_gate_set(self) -> list[str]: + return self._native_gate_set + + @property + def connectivity_graph(self) -> list[str]: + return self._connectivity_graph + + @property + def one_qubit_properties(self) -> dict[str, OneQubitProperties]: + return self._one_qubit_properties + + @property + def two_qubit_properties(self) -> dict[str, TwoQubitProperties]: + return self._two_qubit_properties + + @property + def supported_result_types(self) -> list[ResultType]: + return self._supported_result_types + + @classmethod + def from_device_properties( + cls, device_properties: DeviceCapabilities + ) -> "DeviceEmulatorProperties": + """Create a DeviceEmulatorProperties instance from DeviceCapabilities.""" + + if not isinstance(device_properties, DeviceCapabilities): + raise TypeError("device_properties has to be an instance of DeviceCapabilities.") + + if isinstance(device_properties, IonqDeviceCapabilities): + device_properties = _standardize_ionq_device_properties(device_properties) + + device_properties_json = device_properties.json() + properties_dict = json.loads(device_properties_json) + + required_keys = ["paradigm", "standardized"] + for key in required_keys: + if (key not in properties_dict) or (properties_dict[key] is None): + raise ValueError(f"device_properties_json must have non-empty value for key {key}") + + if "braket.ir.openqasm.program" not in properties_dict["action"]: + raise ValueError( + "The action in device_properties_json must have key `braket.ir.openqasm.program`." + ) + + # Convert dictionary representations to OneQubitProperties and TwoQubitProperties objects + one_qubit_props = {} + for key, value in properties_dict["standardized"]["oneQubitProperties"].items(): + one_qubit_props[key] = OneQubitProperties.parse_obj(value) + + two_qubit_props = {} + for key, value in properties_dict["standardized"]["twoQubitProperties"].items(): + two_qubit_props[key] = TwoQubitProperties.parse_obj(value) + + # Convert dictionary representations to ResultType objects + result_types = [ + ResultType.parse_obj(value) + for value in properties_dict["action"]["braket.ir.openqasm.program"][ + "supportedResultTypes" + ] + ] + + return DeviceEmulatorProperties( + qubitCount=properties_dict["paradigm"]["qubitCount"], + nativeGateSet=properties_dict["paradigm"]["nativeGateSet"], + connectivityGraph=properties_dict["paradigm"]["connectivity"]["connectivityGraph"], + oneQubitProperties=one_qubit_props, + twoQubitProperties=two_qubit_props, + supportedResultTypes=result_types, + ) + + @classmethod + def from_json(cls, device_properties_json: str) -> "DeviceEmulatorProperties": + """Create a DeviceEmulatorProperties instance from a JSON string.""" + + return cls.from_device_properties(BraketSchemaBase.parse_raw_schema(device_properties_json)) diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py new file mode 100644 index 000000000..df8275aba --- /dev/null +++ b/src/braket/emulation/emulator.py @@ -0,0 +1,180 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Iterable +from typing import Any + +from braket.circuits import Circuit +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.measure import Measure +from braket.circuits.noise_model import NoiseModel +from braket.devices import Device +from braket.emulation.pass_manager import PassManager +from braket.emulation.passes import ValidationPass +from braket.tasks import QuantumTask +from braket.tasks.quantum_task import TaskSpecification +from braket.tasks.quantum_task_batch import QuantumTaskBatch + + +class Emulator(Device): + """ + An emulator is a simulation device that more closely resembles the capabilities and constraints + of a real device or of a specific device model. + + Args: + backend (Device): The backend device to use for emulation. + noise_model (NoiseModel, optional): A noise model to apply to the emulated circuits. + Defaults to None. + passes (Iterable[ValidationPass], optional): A list of validation passes to apply to the + emulated circuits. Defaults to None. + """ + + def __init__( + self, + backend: Device, + noise_model: NoiseModel | None = None, + passes: Iterable[ValidationPass] | None = None, + **kwargs, + ): + Device.__init__(self, name=kwargs.get("name", "DeviceEmulator"), status="AVAILABLE") + self._pass_manager = PassManager(passes) + self._noise_model = noise_model + self._backend = backend + + def run( + self, + task_specification: TaskSpecification, + shots: int | None = 0, + inputs: dict[str, float] | None = None, + *args: Any, + **kwargs: Any, + ) -> QuantumTask: + """Emulate a quantum task specification on this quantum device emulator. + A quantum task can be a circuit or an annealing problem. Emulation + involves running all emulator passes on the input program before running + the program on the emulator's backend. + + Args: + task_specification (TaskSpecification): Specification of a quantum task + to run on device. + shots (int, optional): The number of times to run the quantum task on the device. + Default is `None`. + inputs (dict[str, float], optional): Inputs to be passed along with the + IR. If IR is an OpenQASM Program, the inputs will be updated with this value. + Not all devices and IR formats support inputs. Default: {}. + + Returns: + QuantumTask: The QuantumTask tracking task execution on this device emulator. + """ + + task_specification = self.transform(task_specification, apply_noise_model=False) + # Don't apply noise model as the local simulator will automatically apply it. + + # Remove the verbatim box before submitting to the braket density matrix simulator + task_specification_v2 = self._remove_verbatim_box(task_specification) + return self._backend.run(task_specification_v2, shots, inputs, *args, **kwargs) + + def run_batch( + self, + task_specifications: TaskSpecification | list[TaskSpecification], + shots: int | None, + max_parallel: int | None, + inputs: dict[str, float] | list[dict[str, float]] | None, + *args: Any, + **kwargs: Any, + ) -> QuantumTaskBatch: + raise NotImplementedError("Emulator does not support run_batch.") + + @property + def noise_model(self) -> NoiseModel: + """ + An emulator may be defined with a quantum noise model which mimics the noise + on a physical device. A quantum noise model can be defined using the + NoiseModel class. The noise model is applied to Braket Circuits before + running them on the emulator backend. + + Returns: + NoiseModel: This emulator's noise model. + """ + return self._noise_model + + def transform( + self, task_specification: TaskSpecification, apply_noise_model: bool = True + ) -> TaskSpecification: + """ + Passes the input program through all Pass objects contained in this + emulator and applies the emulator's noise model, if it exists, before + returning the compiled program. + + Args: + task_specification (TaskSpecification): The input program to validate and + compile based on this emulator's Passes + apply_noise_model (bool): If true, apply this emulator's noise model + to the compiled program before returning the final program. + + Returns: + TaskSpecification: A compiled program with a noise model applied, if one + exists for this emulator and apply_noise_model is true. + """ + + # Apply measurement manually if the circuit has no measurement and no result type. + # This ensures that the noise model can apply readout error to the circuit, since + # the readout error is applied if and only if there is measurement or result type + # in the circuit. The measurement operations should be added even if apply_noise_model + # is False. + has_measurement = any( + isinstance(instr.operator, Measure) for instr in task_specification.instructions + ) + if (not has_measurement) and len(task_specification.result_types) == 0: + task_specification.measure(target_qubits=task_specification.qubits) + + program = self._pass_manager.transform(task_specification) + return ( + self._noise_model.apply(program) if apply_noise_model and self.noise_model else program + ) + + def _remove_verbatim_box(self, noisy_verbatim_circ: Circuit) -> Circuit: + """ + Remove the verbatim box in the noisy circuit before simulating on + local braket density matrix simulator. + + Args: + noisy_verbatim_circ (Circuit): The input verbatim noisy program + + Returns: + Circuit: A verbatim noisy program without the verbatim boxes + """ + noisy_verbatim_circ_2 = [ + instruction + for instruction in noisy_verbatim_circ.instructions + if not isinstance(instruction.operator, StartVerbatimBox) + and not isinstance(instruction.operator, EndVerbatimBox) + ] + noisy_verbatim_circ_3 = Circuit(noisy_verbatim_circ_2) + for result_type in noisy_verbatim_circ.result_types: + noisy_verbatim_circ_3.add(result_type) + + return noisy_verbatim_circ_3 + + def validate(self, task_specification: TaskSpecification) -> None: + """ + This method passes the input program through Passes that perform + only validation, without modifying the input program. + + Args: + task_specification (TaskSpecification): The program to validate with this + emulator's validation passes. + """ + self._pass_manager.validate(task_specification) diff --git a/src/braket/emulation/local_emulator.py b/src/braket/emulation/local_emulator.py new file mode 100644 index 000000000..543e6f0a5 --- /dev/null +++ b/src/braket/emulation/local_emulator.py @@ -0,0 +1,227 @@ +# 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. + +from __future__ import annotations + +from braket.device_schema.device_capabilities import DeviceCapabilities + +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria +from braket.circuits.noise_model.measure_criteria import MeasureCriteria +from braket.circuits.noises import ( + BitFlip, + Depolarizing, + TwoQubitDepolarizing, +) +from braket.circuits.translations import BRAKET_GATES +from braket.devices.local_simulator import LocalSimulator +from braket.emulation.device_emulator_properties import DeviceEmulatorProperties +from braket.emulation.emulator import Emulator +from braket.emulation.passes._device_emulator_validators import ( + _set_up_connectivity_validator, + _set_up_gate_connectivity_validator, +) +from braket.emulation.passes.circuit_passes import ( + GateValidator, + QubitCountValidator, + ResultTypeValidator, + _NotImplementedValidator, +) + + +class LocalEmulator(Emulator): + """ + A local emulator that mimics the restrictions and noises of a QPU based on the provided device + properties. + """ + + @classmethod + def from_device_properties( + cls, + device_properties: DeviceCapabilities | DeviceEmulatorProperties, + backend: str = "braket_dm", + **kwargs, + ) -> LocalEmulator: + """Create a LocalEmulator instance from device properties. + + Args: + device_properties (DeviceCapabilities, DeviceEmulatorProperties): The device + properties to use for emulation. + backend (str): The backend to use for simulation. Default is "braket_dm". + **kwargs: Additional keyword arguments to pass to the LocalEmulator constructor. + + Returns: + LocalEmulator: A new LocalEmulator instance configured with the given properties. + + Raises: + TypeError: If the device_properties is not a DeviceCapabilities or + DeviceEmulatorProperties object + """ + + # Instantiate an instance of DeviceEmulatorProperties + if isinstance(device_properties, DeviceEmulatorProperties): + device_em_properties = device_properties + elif isinstance(device_properties, DeviceCapabilities): + device_em_properties = DeviceEmulatorProperties.from_device_properties( + device_properties + ) + else: + raise TypeError( + f"device_properties must be an instance of either DeviceCapabilities or " + f"DeviceEmulatorProperties, not {type(device_properties)}." + ) + + # Create a noise model based on the provided device properties + noise_model = cls._setup_basic_noise_model_strategy(device_em_properties) + + passes = [ + _NotImplementedValidator(), + QubitCountValidator(device_em_properties.qubit_count), + GateValidator(native_gates=device_em_properties.native_gate_set), + _set_up_connectivity_validator(device_em_properties), + _set_up_gate_connectivity_validator(device_em_properties), + ResultTypeValidator( + device_em_properties.supported_result_types, + device_em_properties.connectivity_graph, + ), + ] + + local_backend = LocalSimulator(backend=backend, noise_model=noise_model) + return cls(backend=local_backend, noise_model=noise_model, passes=passes, **kwargs) + + @classmethod + def from_json( + cls, + device_properties_json: str, + backend: str = "braket_dm", + **kwargs, + ) -> LocalEmulator: + """Create a LocalEmulator instance from a device properties JSON string. + + Args: + device_properties_json (str): Device properties JSON string. + backend (str): The backend to use for simulation. Defaults to "braket_dm". + **kwargs: Additional keyword arguments to pass to the LocalEmulator constructor. + + Returns: + LocalEmulator: A new LocalEmulator instance configured with the given properties. + """ + + device_emu_properties = DeviceEmulatorProperties.from_json(device_properties_json) + return cls.from_device_properties(device_emu_properties, backend=backend, **kwargs) + + @classmethod + def _setup_basic_noise_model_strategy( + cls, device_em_properties: DeviceEmulatorProperties + ) -> NoiseModel: + """ + Apply a basic noise model strategy consisting of: + - 1 Qubit RB Depolarizing Noise + - 1 Qubit Readout Error + - 2 Qubit Gate Depolarizing Noise + + Args: + device_em_properties (DeviceEmulatorProperties): The device emulator properties. + + Returns: + NoiseModel: A noise model configured with the appropriate noise channels. + """ + noise_model = NoiseModel() + + # Add one-qubit noise channels + cls._add_one_qubit_noise(noise_model, device_em_properties) + + # Add two-qubit noise channels + cls._add_two_qubit_noise(noise_model, device_em_properties) + + return noise_model + + @classmethod + def _add_one_qubit_noise( + cls, noise_model: NoiseModel, device_em_properties: DeviceEmulatorProperties + ) -> None: + """ + Add one-qubit noise channels to the noise model: + - One-qubit depolarizing noise based on RB fidelity + - One-qubit readout error + + Args: + noise_model (NoiseModel): The noise model to add noise channels to. + device_em_properties (DeviceEmulatorProperties): The device emulator properties. + """ + for qubit_str, data in device_em_properties.one_qubit_properties.items(): + qubit = int(qubit_str) + oneQubitProperty = data.oneQubitFidelity + fidelity_names = { + fidelity.fidelityType.name: ind for ind, fidelity in enumerate(oneQubitProperty) + } + + # Apply one qubit RB Depolarizing Noise + if "RANDOMIZED_BENCHMARKING" in fidelity_names: + one_qubit_fidelity = oneQubitProperty[ + fidelity_names["RANDOMIZED_BENCHMARKING"] + ].fidelity + elif "SIMULTANEOUS_RANDOMIZED_BENCHMARKING" in fidelity_names: + one_qubit_fidelity = oneQubitProperty[ + fidelity_names["SIMULTANEOUS_RANDOMIZED_BENCHMARKING"] + ].fidelity + else: + raise ValueError( + f"No valid one-qubit RB data found for qubit {qubit} in oneQubitProperties." + ) + + one_qubit_depolarizing_rate = 1 - one_qubit_fidelity + noise_model.add_noise( + Depolarizing(one_qubit_depolarizing_rate), GateCriteria(qubits=qubit) + ) + + # Apply one qubit READOUT noise + readout_error_rate = 1 - oneQubitProperty[fidelity_names["READOUT"]].fidelity + noise_model.add_noise(BitFlip(readout_error_rate), ObservableCriteria(qubits=qubit)) + noise_model.add_noise(BitFlip(readout_error_rate), MeasureCriteria(qubits=qubit)) + + @classmethod + def _add_two_qubit_noise( + cls, noise_model: NoiseModel, device_em_properties: DeviceEmulatorProperties + ) -> None: + """ + Add two-qubit noise channels to the noise model: + - Two-qubit depolarizing noise based on gate fidelity + + Args: + noise_model (NoiseModel): The noise model to add noise channels to. + device_em_properties (DeviceEmulatorProperties): The device emulator properties. + """ + for edge, data in device_em_properties.two_qubit_properties.items(): + qubits = [int(qubit) for qubit in edge.split("-")] + twoQubitGateFidelity = data.twoQubitGateFidelity + + valid_gate_names = { + gate_fidelity.gateName.lower(): ind + for ind, gate_fidelity in enumerate(twoQubitGateFidelity) + if gate_fidelity.gateName.lower() in BRAKET_GATES + } + + if not valid_gate_names: + raise ValueError( + f"No valid two-qubit RB data found for edge {edge} in twoQubitProperties." + ) + + # Apply two qubit RB Depolarizing Noise + for gate_name, gate_ind in valid_gate_names.items(): + gate_fidelity = twoQubitGateFidelity[gate_ind] + two_qubit_depolarizing_rate = 1 - gate_fidelity.fidelity + gate = BRAKET_GATES[gate_name] + noise_model.add_noise( + TwoQubitDepolarizing(two_qubit_depolarizing_rate), + GateCriteria(gate, [(qubits[0], qubits[1]), (qubits[1], qubits[0])]), + ) diff --git a/src/braket/emulation/pass_manager.py b/src/braket/emulation/pass_manager.py new file mode 100644 index 000000000..4d6ef8712 --- /dev/null +++ b/src/braket/emulation/pass_manager.py @@ -0,0 +1,70 @@ +# 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. + +from __future__ import annotations + +from collections.abc import Iterable + +from braket.emulation.passes import ValidationPass +from braket.tasks.quantum_task import TaskSpecification + + +class EmulatorValidationError(Exception): + """Custom exception validation errors from emulators.""" + + +class PassManager: + def __init__(self, passes: Iterable[ValidationPass] | None = None): + self._passes = passes if passes is not None else [] + + def transform(self, task_specification: TaskSpecification) -> TaskSpecification: + """ + This method passes the input program through the Passes contained + within this pass manager. A pass may simply validate a program or may + modify or entirely transform the program (to an equivalent quantum program). + + Args: + task_specification (TaskSpecification): The program to run the emulator passes on. + + Returns: + TaskSpecification: A "compiled" program of the same type as the input. + + """ + for emulator_pass in self._passes: + task_specification = emulator_pass(task_specification) + return task_specification + + def validate(self, task_specification: TaskSpecification) -> None: + """ + This method passes the input program through Passes that perform + only validation, without modifying the input program. + + Args: + task_specification (TaskSpecification): The program to validate with this + emulator's validation passes. + """ + + try: + for emulator_pass in self._passes: + emulator_pass(task_specification) + except Exception as e: + self._raise_exception(e) + + def _raise_exception(self, exception: Exception) -> None: + """ + Wrapper for exceptions enable modifyint the exception message if needed. + + Args: + exception (Exception): The exception to modify and raise. + """ + raise EmulatorValidationError(str(exception)) from exception diff --git a/src/braket/emulation/passes/__init__.py b/src/braket/emulation/passes/__init__.py new file mode 100644 index 000000000..fc6ad3904 --- /dev/null +++ b/src/braket/emulation/passes/__init__.py @@ -0,0 +1,14 @@ +# 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. + +from braket.emulation.passes.validation_pass import ValidationPass # noqa: F401 diff --git a/src/braket/emulation/passes/_device_emulator_validators.py b/src/braket/emulation/passes/_device_emulator_validators.py new file mode 100644 index 000000000..2067d7080 --- /dev/null +++ b/src/braket/emulation/passes/_device_emulator_validators.py @@ -0,0 +1,65 @@ +# 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. + +from braket.circuits.translations import BRAKET_GATES +from braket.emulation.device_emulator_properties import DeviceEmulatorProperties +from braket.emulation.passes.circuit_passes import ConnectivityValidator, GateConnectivityValidator + + +def _set_up_connectivity_validator( + device_emu_properties: DeviceEmulatorProperties, +) -> ConnectivityValidator: + if device_emu_properties.fully_connected: + return ConnectivityValidator( + qubit_labels=device_emu_properties.qubit_labels, + fully_connected=True, + directed=False, + # Set directed to false because ConnectivityValidator validates + # the connectivity regardless if the graph is directed or undirected. + ) + return ConnectivityValidator( + connectivity_graph=device_emu_properties.connectivity_graph, + num_qubits=device_emu_properties.qubit_count, + qubit_labels=device_emu_properties.qubit_labels, + directed=False, + # Set directed to false because ConnectivityValidator validates + # the connectivity regardless if the graph is directed or undirected. + ) + + +def _set_up_gate_connectivity_validator( + device_emu_properties: DeviceEmulatorProperties, +) -> GateConnectivityValidator: + directed = device_emu_properties.directed + + if device_emu_properties.fully_connected: + gate_connectivity_graph = {} + for qubit_1 in device_emu_properties.qubit_labels: + for qubit_2 in device_emu_properties.qubit_labels: + gate_connectivity_graph[qubit_1, qubit_2] = set( + device_emu_properties.native_gate_set + ) + else: + # For non fully connected graph + gate_connectivity_graph = {} + for edge, edge_property in device_emu_properties.two_qubit_properties.items(): + vertices = edge.split("-") + edge_int = tuple(int(vertex) for vertex in vertices) + edge_supported_gates = [ + item.gateName.lower() + for item in edge_property.twoQubitGateFidelity + if item.gateName.lower() in BRAKET_GATES + ] + gate_connectivity_graph[edge_int] = set(edge_supported_gates) + + return GateConnectivityValidator(gate_connectivity_graph, directed=directed) diff --git a/src/braket/emulation/passes/circuit_passes/__init__.py b/src/braket/emulation/passes/circuit_passes/__init__.py new file mode 100644 index 000000000..603488d3d --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/__init__.py @@ -0,0 +1,29 @@ +# 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. + +from braket.emulation.passes.circuit_passes.connectivity_validator import ( # noqa: F401 + ConnectivityValidator, +) +from braket.emulation.passes.circuit_passes.gate_connectivity_validator import ( # noqa: F401 + GateConnectivityValidator, +) +from braket.emulation.passes.circuit_passes.gate_validator import GateValidator # noqa: F401 +from braket.emulation.passes.circuit_passes.not_implemented_validator import ( # noqa: F401 + _NotImplementedValidator, +) +from braket.emulation.passes.circuit_passes.qubit_count_validator import ( # noqa: F401 + QubitCountValidator, +) +from braket.emulation.passes.circuit_passes.result_type_validator import ( # noqa: F401 + ResultTypeValidator, +) diff --git a/src/braket/emulation/passes/circuit_passes/connectivity_validator.py b/src/braket/emulation/passes/circuit_passes/connectivity_validator.py new file mode 100644 index 000000000..cd4ea2096 --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/connectivity_validator.py @@ -0,0 +1,174 @@ +# 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. + +from collections.abc import Iterable + +from networkx import DiGraph, complete_graph, from_dict_of_lists + +from braket.circuits import Circuit +from braket.circuits.compiler_directives import StartVerbatimBox +from braket.circuits.gate import Gate +from braket.emulation.passes import ValidationPass +from braket.registers.qubit_set import QubitSet + + +class ConnectivityValidator(ValidationPass): + def __init__( + self, + connectivity_graph: dict[int, Iterable[int]] | DiGraph | None = None, + fully_connected: bool = False, + num_qubits: int | None = None, + qubit_labels: Iterable[int] | QubitSet | None = None, + directed: bool = True, + ): + """ + A ConnectivityValidator instance takes in a qubit connectivity graph and validates that + a circuit that uses verbatim circuits makes valid hardware qubit references in single + and two-qubit gate operations. + + Args: + connectivity_graph (dict[int, Iterable[int]], DiGraph, optional): + Either a sparse matrix or DiGraph representation of the device connectivity. + Can be None if fully_connected is true. + + fully_connected (bool): If true, the all qubits in the device are connected. + + num_qubits (int, optional): The number of qubits in the device; if fully_connected is + True, create a complete graph with num_qubits nodes; ignored if + connectivity_graph is provided and fully_connected if False. + + qubit_labels (Iterable[int], QubitSet, optional): A set of qubit labels; if + fully_connected is True, the qubits_labels are used as nodes of a fully connected + topology; ignored if connectivity_graph is provided and fully_connected if False. + + directed (bool): Denotes if the connectivity graph is directed or undirected. If + the connectivity graph is undirected, this constructor attempts to fill in any + missing back edges. + + Raises: + ValueError: If the inputs do not correctly yield a connectivity graph; i.e. + fully_connected is true but neither/both num qubits and qubit labels are defined + or a valid DiGraph or dict representation of a connectivity graph is not provided. + """ + + if not ((connectivity_graph is not None) ^ fully_connected): + raise ValueError( + "Either the connectivity_graph must be provided OR fully_connected must be True\ + (not both)." + ) + + if fully_connected: + if not ((num_qubits is None) ^ (qubit_labels is None)): + raise ValueError( + "Either num_qubits or qubit_labels (NOT both) must be \ + provided if fully_connected is True." + ) + self._connectivity_graph = complete_graph( + num_qubits or qubit_labels, create_using=DiGraph() + ) + elif not isinstance(connectivity_graph, DiGraph): + try: + self._connectivity_graph = from_dict_of_lists( + connectivity_graph, create_using=DiGraph() + ) + except Exception as e: + raise ValueError( + f"connectivity_graph must be a valid DiGraph or a dictionary\ + mapping integers (nodes) to a list of integers (adjancency lists): {e}" + ) from e + else: + self._connectivity_graph = connectivity_graph + + if not directed: + for edge in self._connectivity_graph.edges: + self._connectivity_graph.add_edge(edge[1], edge[0]) + + def _graph_node_type(self) -> type: + return type(next(iter(self._connectivity_graph.nodes))) + + def validate(self, circuit: Circuit) -> None: + """ + Verifies that any verbatim box in a circuit is runnable with respect to the + device connectivity definied by this validator. If any sub-circuit of the + input circuit is verbatim, we validate the connectivity of all gate operations + in the circuit. + + Args: + circuit (Circuit): The Braket circuit whose gate operations to + validate. + + Raises: + ValueError: If a hardware qubit reference does not exist in the connectivity graph. + """ + # If any of the instructions are in verbatim mode, all qubit references + # must point to hardware qubits. Otherwise, this circuit need not be validated. + if not any( + isinstance(instruction.operator, StartVerbatimBox) + for instruction in circuit.instructions + ): + return + for idx in range(len(circuit.instructions)): + instruction = circuit.instructions[idx] + if isinstance(instruction.operator, Gate): + if ( + instruction.operator.qubit_count == 2 + ): # Assuming only maximum 2-qubit native gates are supported + self._validate_instruction_connectivity(instruction.control, instruction.target) + else: + # just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if self._graph_node_type()(int(target_qubit)) not in self._connectivity_graph: + raise ValueError( + f"Qubit {target_qubit} does not exist in the device topology." + ) + + def _validate_instruction_connectivity( + self, control_qubits: QubitSet, target_qubits: QubitSet + ) -> None: + """ + Checks if a two-qubit instruction is valid based on this validator's connectivity + graph. + + Args: + control_qubits (QubitSet): The control qubits used in this multi-qubit + operation. + target_qubits (QubitSet): The target qubits of this operation. For many gates, + both the control and target are stored in "target_qubits", so we may + see target_qubits have length 2. + + Raises: + ValueError: If any two-qubit gate operation uses a qubit edge that does not exist + in the qubit connectivity graph. + """ + # Create edges between each of the target qubits + gate_connectivity_graph = DiGraph() + # Create an edge from each control bit to each target qubit + if len(control_qubits) == 1 and len(target_qubits) == 1: + gate_connectivity_graph.add_edge(control_qubits[0], target_qubits[0]) + elif len(control_qubits) == 0 and len(target_qubits) == 2: + gate_connectivity_graph.add_edges_from([ + (target_qubits[0], target_qubits[1]), + (target_qubits[1], target_qubits[0]), + ]) + else: + raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") + # Check that each edge exists in this validator's connectivity graph + for edge in gate_connectivity_graph.edges: + typed_edge = ( + self._graph_node_type()(int(edge[0])), + self._graph_node_type()(int(edge[1])), + ) + if not self._connectivity_graph.has_edge(*typed_edge): + raise ValueError( + f"{typed_edge[0]} is not connected to qubit {typed_edge[1]} in this device." + ) diff --git a/src/braket/emulation/passes/circuit_passes/gate_connectivity_validator.py b/src/braket/emulation/passes/circuit_passes/gate_connectivity_validator.py new file mode 100644 index 000000000..6599ec3be --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/gate_connectivity_validator.py @@ -0,0 +1,177 @@ +# 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. + +from collections.abc import Iterable +from typing import Any + +from networkx import DiGraph + +from braket.circuits.circuit import Circuit +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.gate import Gate +from braket.emulation.passes import ValidationPass +from braket.registers.qubit_set import QubitSet + + +class GateConnectivityValidator(ValidationPass): + def __init__( + self, + gate_connectivity_graph: dict[tuple[Any, Any], Iterable[str]] | DiGraph, + directed: bool = True, + ): + """ + A GateConnectivityValidator instance takes in a gate connectivity graph and validates that + a circuit that uses verbatim circuits makes valid hardware gate references in single + and two-qubit gate operations on the corresponding edges. + + Args: + gate_connectivity_graph (dict[tuple[Any, Any], Iterable[str]], DiGraph, optional): + Either a sparse matrix or DiGraph representation of the supported gates for the + edges on the device. + directed (bool): Denotes if the connectivity graph is directed or undirected. If + the connectivity graph is undirected, this constructor attempts to fill in any + missing back edges. + + Raises: + ValueError: If the inputs do not correctly yield a gate connectivity graph. + """ + super().__init__() + if isinstance(gate_connectivity_graph, dict): + self._gate_connectivity_graph = DiGraph() + for (u, v), supported_gates in gate_connectivity_graph.items(): + self._gate_connectivity_graph.add_edge(u, v, supported_gates=supported_gates) + elif isinstance(gate_connectivity_graph, DiGraph): + self._gate_connectivity_graph = gate_connectivity_graph + else: + raise TypeError( + "Gate_connectivity_graph must either be a dictionary of edges mapped to \ +supported gates lists, or a DiGraph with supported gates \ +provided as edge attributes." + ) + + if not directed: + """ + Add reverse edges and check that any supplied reverse edges have + identical supported gate sets to their corresponding forwards edge. + """ + for u, v in self._gate_connectivity_graph.edges: + back_edge = (v, u) + if back_edge not in self._gate_connectivity_graph.edges: + supported_gates = self._gate_connectivity_graph[u][v]["supported_gates"] + self._gate_connectivity_graph.add_edge( + *back_edge, supported_gates=supported_gates + ) + # check that the supported gate sets are identical + elif ( + self._gate_connectivity_graph[u][v]["supported_gates"] + != self._gate_connectivity_graph[v][u]["supported_gates"] + ): + raise ValueError( + f"Connectivity Graph marked as undirected\ + but edges ({u}, {v}) and ({v}, {u}) have different supported\ + gate sets." + ) + + def _graph_node_type(self) -> type: + return type(next(iter(self._gate_connectivity_graph.nodes))) + + def validate(self, circuit: Circuit) -> None: + """ + Verifies that any multiqubit gates used within a verbatim box are supported + by the devices gate connectivity defined by this criteria. + + Args: + circuit (Circuit): The circuit whose gate instructions need to be validated + against this validator's gate connectivity graph. + + Raises: + ValueError if any of the gate operations use qubits or qubit edges that don't exist + in the qubit connectivity graph or the gate operation is not supported by the edge. + """ + + in_verbatim = False + for instruction in circuit.instructions: + operator = instruction.operator + if isinstance(operator, Gate) and in_verbatim: + self._validate_gate_in_verbatim(instruction) + elif isinstance(operator, StartVerbatimBox): + if in_verbatim: + raise ValueError("Already in verbatim box") + in_verbatim = True + elif isinstance(operator, EndVerbatimBox): + if not in_verbatim: + raise ValueError("Already outside of verbatim box") + in_verbatim = False + + # Check for unclosed verbatim box + if in_verbatim: + raise ValueError("No end verbatim box found for the circuit.") + + def _validate_gate_in_verbatim(self, instruction: Any) -> None: + """ + Validate a gate instruction within a verbatim box. + + Args: + instruction: The gate instruction to validate + + Raises: + ValueError: If the gate is not supported by the device topology + """ + if ( + instruction.operator.qubit_count == 2 + ): # Assuming only maximum 2-qubit native gates are supported + self._validate_instruction_connectivity( + instruction.operator.name, instruction.control, instruction.target + ) + else: + # just check that the target qubit exists in the connectivity graph + target_qubit = instruction.target[0] + if self._graph_node_type()(int(target_qubit)) not in self._gate_connectivity_graph: + raise ValueError(f"Qubit {target_qubit} does not exist in the device topology.") + + def _validate_instruction_connectivity( + self, gate_name: str, control_qubits: QubitSet, target_qubits: QubitSet + ) -> None: + """ + Checks if a specific is able to be applied to the control and target qubits based + on this validator's gate connectivity graph. + + Args: + gate_name (str): The name of the gate being applied. + control_qubits (QubitSet): The set of control qubits used by this gate operation. + target_qubits (QubitSet): The set of target qubits used by this gate operation. + + Raises: + ValueError if the gate operation is not possible on the qubit connectivity graph. + """ + # Create edges between each of the target qubits + if len(control_qubits) == 1 and len(target_qubits) == 1: + e = (control_qubits[0], target_qubits[0]) + elif len(control_qubits) == 0 and len(target_qubits) == 2: + e = (target_qubits[0], target_qubits[1]) + else: + raise ValueError("Unrecognized qubit targetting setup for a 2 qubit gate.") + + e = (self._graph_node_type()(int(e[0])), self._graph_node_type()(int(e[1]))) + + # Check that each edge exists in this validator's connectivity graph + if self._gate_connectivity_graph.has_edge(*e): + supported_gates = self._gate_connectivity_graph[e[0]][e[1]]["supported_gates"] + else: + raise ValueError(f"{e[0]} is not connected to {e[1]} on this device.") + + supported_gates = [gate.lower() for gate in supported_gates] + if gate_name.lower() not in supported_gates: + raise ValueError( + f"Qubit pair ({e[0]}, {e[1]}) does not support gate {gate_name} on this device." + ) diff --git a/src/braket/emulation/passes/circuit_passes/gate_validator.py b/src/braket/emulation/passes/circuit_passes/gate_validator.py new file mode 100644 index 000000000..5aebcd5da --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/gate_validator.py @@ -0,0 +1,97 @@ +# 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. + +from collections.abc import Iterable + +from braket.circuits import Circuit +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.gate import Gate +from braket.circuits.translations import BRAKET_GATES +from braket.emulation.passes import ValidationPass + + +class GateValidator(ValidationPass): + def __init__( + self, + supported_gates: Iterable[str] | None = None, + native_gates: Iterable[str] | None = None, + ): + """ + A GateValidator instance validates that a circuit uses the supported gates of the device, or + native gates within a verbatim box if any. + + Args: + supported_gates (Iterable[str], optional): A list of gates supported outside of + verbatim modeby the emulator. A gate is a Braket gate name. + native_gates (Iterable[str], optional): A list of gates supported inside of + verbatim mode by the emulator. + + Raises: + ValueError: If supported_gates and native_gates are empty or any of the provided + gate are not supported by the Braket BDK. + """ + supported_gates, native_gates = (supported_gates or []), (native_gates or []) + if not supported_gates and not native_gates: + raise ValueError("Supported gate set or native gate set must be provided.") + + try: + self._supported_gates = frozenset( + BRAKET_GATES[gate.lower()] for gate in supported_gates + ) + except KeyError as e: + raise ValueError( + f"Input {e!s} in supported_gates is not a valid Braket gate name." + ) from e + + try: + self._native_gates = frozenset(BRAKET_GATES[gate.lower()] for gate in native_gates) + except KeyError as e: + raise ValueError(f"Input {e!s} in native_gates is not a valid Braket gate name.") from e + + def validate(self, circuit: Circuit) -> None: + """ + Checks that all non-verbatim gates used in the circuit are in this validator's + supported gate set and that all verbatim gates used in the circuit are in this + validator's native gate set. + + Args: + circuit (Circuit): The Braket circuit whose gates to validate. + + Raises: + ValueError: If a gate operation or verbatim gate operation is not in this validator's + supported or native gate set, respectively. + """ + in_verbatim = False + for instruction in circuit.instructions: + operator = instruction.operator + if isinstance(operator, Gate): + self._validate_gate(in_verbatim, operator) + elif isinstance(operator, StartVerbatimBox): + if in_verbatim: + raise ValueError("Already in verbatim box") + in_verbatim = True + elif isinstance(operator, EndVerbatimBox): + if not in_verbatim: + raise ValueError("Already outside of verbatim box") + in_verbatim = False + + # Check for unclosed verbatim box + if in_verbatim: + raise ValueError("No end verbatim box found for the circuit.") + + def _validate_gate(self, in_verbatim: bool, operator: Gate) -> None: + if in_verbatim: + if type(operator) not in self._native_gates: + raise ValueError(f"Gate {operator.name} is not a native gate for this device.") + elif type(operator) not in self._supported_gates: + raise ValueError(f"Gate {operator.name} is not a supported gate for this device.") diff --git a/src/braket/emulation/passes/circuit_passes/not_implemented_validator.py b/src/braket/emulation/passes/circuit_passes/not_implemented_validator.py new file mode 100644 index 000000000..af9ad8d75 --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/not_implemented_validator.py @@ -0,0 +1,54 @@ +# 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. + +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.emulation.passes import ValidationPass +from braket.program_sets import ProgramSet +from braket.tasks.quantum_task import TaskSpecification + + +class _NotImplementedValidator(ValidationPass): + """ + A validator that checks for features that are not implemented in the emulator. + Currently checks for: + 1. Verbatim boxes - raises an error if the circuit does not have a verbatim box + 2. ProgramSet - raises an error if the program is a ProgramSet + """ + + def validate(self, program: TaskSpecification) -> None: + """ + Validates that the program does not contain any unsupported features. + + Args: + program (TaskSpecification): The program to validate. + + Raises: + ValueError: If the program does not have a verbatim box when required. + TypeError: If the program is a ProgramSet + """ + + # Validate out ProgramSet + if isinstance(program, ProgramSet): + raise TypeError("ProgramSet is not supported yet.") + + # Check if the program has a verbatim box when required + has_verbatim_box = any( + isinstance(instruction.operator, (StartVerbatimBox, EndVerbatimBox)) + for instruction in program.instructions + ) + + if not has_verbatim_box: + raise ValueError( + "The input circuit must have a verbatim box. " + "Add a verbatim box to the circuit, and try again." + ) diff --git a/src/braket/emulation/passes/circuit_passes/qubit_count_validator.py b/src/braket/emulation/passes/circuit_passes/qubit_count_validator.py new file mode 100644 index 000000000..825defa71 --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/qubit_count_validator.py @@ -0,0 +1,50 @@ +# 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. + +from braket.circuits import Circuit +from braket.emulation.passes import ValidationPass + + +class QubitCountValidator(ValidationPass): + def __init__(self, qubit_count: int): + """ + A QubitCountValidator instance validates that a circuit does not use more qubits + than available on a device. + + Args: + qubit_count (int): The number of qubits on the device + + Raises: + ValueError: If the circuit uses more qubits than available on a device. + """ + if qubit_count <= 0: + raise ValueError(f"qubit_count ({qubit_count}) must be a positive integer.") + self._qubit_count = qubit_count + + def validate(self, circuit: Circuit) -> None: + """ + Checks that the number of qubits used in this circuit does not exceed this + validator's qubit_count max. + + Args: + circuit (Circuit): The Braket circuit whose qubit count to validate. + + Raises: + ValueError: If the number of qubits used in the circuit exceeds the qubit_count. + + """ + if circuit.qubit_count > self._qubit_count: + raise ValueError( + f"Circuit must use at most {self._qubit_count} qubits, \ +but uses {circuit.qubit_count} qubits." + ) diff --git a/src/braket/emulation/passes/circuit_passes/result_type_validator.py b/src/braket/emulation/passes/circuit_passes/result_type_validator.py new file mode 100644 index 000000000..8741acf2d --- /dev/null +++ b/src/braket/emulation/passes/circuit_passes/result_type_validator.py @@ -0,0 +1,94 @@ +# 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. + +from collections.abc import Iterable + +from braket.device_schema.result_type import ResultType + +from braket.circuits import Circuit +from braket.circuits.result_type import ObservableResultType +from braket.emulation.passes import ValidationPass + + +class ResultTypeValidator(ValidationPass): + def __init__( + self, + supported_result_types: Iterable[ResultType] | None = None, + connectivity_graph: dict[str, list[str]] | None = None, + ): + """ + A ResultTypeValidator instance validates that the result types of a circuit are supported on + the device. + + Args: + supported_result_types (Optional[Iterable[str]]): A list of result types supported + by the emulator. A result type is a Braket result type name. + connectivity_graph (Dict[str, List[str]]): Graph representing qubit + connectivity. The keys are qubit indices as strings, and the values are lists + of neighboring qubit indices as strings. + + Raises: + ValueError: If supported_result_types is empty or None. + ValueError: If connectivity_graph is None. + """ + if not supported_result_types: + raise ValueError("Supported result types must be provided.") + if connectivity_graph is None: + raise ValueError("Connectivity graph must be provided.") + + self._supported_result_types = { + result_type.name: result_type.observables for result_type in supported_result_types + } + + self._connectivity_graph = connectivity_graph + + def validate(self, circuit: Circuit) -> None: + """ + Checks that all result types used in the circuit are in this validator's + supported result types set and that the target qubits are valid qubits in the device. + + Args: + circuit (Circuit): The Braket circuit whose result types to validate. + + Raises: + ValueError: If a result type is not in this validator's supported result types set + or if a target qubit is not a valid qubit in the device. + """ + for result_type in circuit.result_types: + if result_type.name not in self._supported_result_types: + raise ValueError( + f"The result type {result_type.name} is not a supported " + f"result type for this device. Check the device documentation " + f"for a list of supported result types." + ) + + if isinstance(result_type, ObservableResultType): + observable_name = result_type.observable.name.lower() + if observable_name not in self._supported_result_types[result_type.name]: + raise ValueError( + f"Observable {observable_name} is not supported for result type " + f"{result_type.name} on this device. Supported observables are: " + f"{self._supported_result_types[result_type.name]}." + ) + + # Check if target qubits are valid qubits in the device + target = result_type.target + for qubit in target: + qubit_str = str(int(qubit)) + if qubit_str not in self._connectivity_graph: + raise ValueError( + f"Qubit {int(qubit)} in result type {result_type.name} " + f"is not a valid qubit for this device. " + f"The set of valid qubits can be found in the connectivity graph " + f"of the device." + ) diff --git a/src/braket/emulation/passes/validation_pass.py b/src/braket/emulation/passes/validation_pass.py new file mode 100644 index 000000000..fd716f783 --- /dev/null +++ b/src/braket/emulation/passes/validation_pass.py @@ -0,0 +1,49 @@ +# 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. + +from __future__ import annotations + +from abc import ABC, abstractmethod + +from braket.tasks.quantum_task import TaskSpecification + + +class ValidationPass(ABC): + @abstractmethod + def validate(self, task_specification: TaskSpecification) -> None: + """ + An emulator validator is used to perform some non-modifying validation + pass on an input program. Implementations of validate should return + nothing if the input program passes validation and raise an error otherwise. + + Args: + task_specification (TaskSpecification): The program to be evaluated against this + criteria. + """ + raise NotImplementedError + + def run(self, task_specification: TaskSpecification) -> TaskSpecification: + """ + Validate the input program and return the program, unmodified. + + Args: + task_specification (TaskSpecification): The program to validate. + + Returns: + TaskSpecification: The unmodified program passed in as input. + """ + self.validate(task_specification) + return task_specification + + def __call__(self, task_specification: TaskSpecification) -> TaskSpecification: + return self.run(task_specification) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index 603a4005b..e2f228463 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -2280,3 +2280,216 @@ def test_run_program_set_default_shots(aws_quantum_task_mock, aws_session_init, _ = device.run(program_set) assert aws_quantum_task_mock.call_args_list[0][0][2] == program_set assert aws_quantum_task_mock.call_args_list[0][0][4] == -1 + + +@patch("braket.aws.aws_device.AwsSession") +def test_attempt_get_emulator_with_simulators(aws_session_init, aws_session): + arn = SV1_ARN + aws_session_init.return_value = aws_session + aws_session.get_device.return_value = MOCK_GATE_MODEL_SIMULATOR + device = AwsDevice(arn) + error_message = "Creating an emulator from a Braket managed simulator is not supported." + with pytest.raises(ValueError, match=error_message): + emulator = device.emulator() + + +MOCK_STANDARDIZED_CALIBRATION_JSON_2 = { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": { + "T1": {"value": 0.5, "standardError": None, "unit": "S"}, + "T2": {"value": 0.2, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.99, + "standardError": 1e-2, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.9934, + "standardError": 0.0065, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + }, + ], + }, + "1": { + "T1": {"value": 0.97, "standardError": None, "unit": "S"}, + "T2": {"value": 0.234, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.9983, + "standardError": 4e-5, + }, + { + "fidelityType": { + "name": "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "description": None, + }, + "fidelity": 0.879, + "standardError": 0.00058, + }, + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.989, + "standardError": None, + }, + ], + }, + "2": { + "T1": {"value": 0.8, "standardError": None, "unit": "S"}, + "T2": {"value": 0.4, "standardError": None, "unit": "S"}, + "oneQubitFidelity": [ + { + "fidelityType": {"name": "READOUT", "description": None}, + "fidelity": 0.958, + "standardError": None, + }, + { + "fidelityType": {"name": "RANDOMIZED_BENCHMARKING", "description": None}, + "fidelity": 0.9983, + "standardError": 4e-5, + }, + ], + }, + }, + "twoQubitProperties": { + "0-1": { + "twoQubitGateFidelity": [ + { + "direction": None, + "gateName": "CZ", + "fidelity": 0.9358, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "Two_Qubit_Clifford", + "fidelity": 0.9, + "standardError": 0.0237, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + { + "direction": None, + "gateName": "CPhaseShift", + "fidelity": 0.9, + "standardError": 0.01437, + "fidelityType": {"name": "INTERLEAVED_RANDOMIZED_BENCHMARKING"}, + }, + ] + } + }, +} + + +MOCK_RIGETTI_QPU_CAPABILITIES_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "service": { + "executionWindows": [ + { + "executionDay": "Everyday", + "windowStartHour": "11:00", + "windowEndHour": "12:00", + } + ], + "shotsRange": [1, 10], + }, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "version": ["1"], + "supportedOperations": ["H", "X", "CNot", "CZ", "Rx", "Ry", "YY"], + "supportedResultTypes": [ + {"maxShots": 20000, "minShots": 1, "name": "Probability", "observables": None} + ], + } + }, + "paradigm": { + "qubitCount": 3, + "nativeGateSet": ["cz", "prx", "cphaseshift"], + "connectivity": { + "fullyConnected": False, + "connectivityGraph": {"0": ["1", "2"], "1": ["0"], "2": ["0"]}, + }, + }, + "standardized": MOCK_STANDARDIZED_CALIBRATION_JSON_2, + "deviceParameters": {}, +} + + +@pytest.fixture +def rigetti_device_capabilities(): + return RigettiDeviceCapabilities.parse_obj(MOCK_RIGETTI_QPU_CAPABILITIES_1) + + +MOCK_DEFAULT_S3_DESTINATION_FOLDER = ( + "amazon-braket-us-test-1-00000000", + "tasks", +) + + +@pytest.fixture +def mock_rigetti_qpu_device(rigetti_device_capabilities): + return { + "deviceName": "Ankaa-2", + "deviceType": "QPU", + "providerName": "Rigetti", + "deviceStatus": "OFFLINE", + "deviceCapabilities": rigetti_device_capabilities.json(), + "deviceQueueInfo": [ + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "19", "queuePriority": "Normal"}, + {"queue": "QUANTUM_TASKS_QUEUE", "queueSize": "3", "queuePriority": "Priority"}, + {"queue": "JOBS_QUEUE", "queueSize": "0 (3 prioritized job(s) running)"}, + ], + } + + +@pytest.fixture +def aws_session(): + _boto_session = Mock() + _boto_session.region_name = RIGETTI_REGION + _boto_session.profile_name = "test-profile" + + creds = Mock() + creds.method = "other" + _boto_session.get_credentials.return_value = creds + + _aws_session = Mock() + _aws_session.boto_session = _boto_session + _aws_session._default_bucket = MOCK_DEFAULT_S3_DESTINATION_FOLDER[0] + _aws_session.default_bucket.return_value = _aws_session._default_bucket + _aws_session._custom_default_bucket = False + _aws_session.account_id = "00000000" + _aws_session.region = RIGETTI_REGION + return _aws_session + + +@pytest.fixture +def rigetti_device(aws_session, mock_rigetti_qpu_device): + def _device(): + aws_session.get_device.return_value = mock_rigetti_qpu_device + aws_session.search_devices.return_value = [mock_rigetti_qpu_device] + return AwsDevice(RIGETTI_ARN, aws_session) + + return _device() + + +def test_local_emulator(rigetti_device): + emulator = rigetti_device.emulator() + emulator_v2 = rigetti_device.emulator() + assert emulator == emulator_v2 diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 88a71cfa8..caefc83d8 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -13,6 +13,7 @@ import json import math +import sys import textwrap import warnings from typing import Any, Optional @@ -493,20 +494,25 @@ def properties(self) -> DeviceCapabilities: return RydbergSimulatorDeviceCapabilities.parse_obj(properties) -mock_circuit_entry = Mock() -mock_program_entry = Mock() -mock_jaqcd_entry = Mock() -mock_circuit_dm_entry = Mock() -mock_circuit_entry.load.return_value = DummyCircuitSimulator -mock_program_entry.load.return_value = DummyProgramSimulator -mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator -mock_circuit_dm_entry.load.return_value = DummyProgramDensityMatrixSimulator -local_simulator._simulator_devices = { - "dummy": mock_circuit_entry, - "dummy_oq3": mock_program_entry, - "dummy_jaqcd": mock_jaqcd_entry, - "dummy_oq3_dm": mock_circuit_dm_entry, -} +@pytest.fixture(autouse=True) +def _simulator_devices(request): + if request.module == sys.modules[__name__]: + mock_circuit_entry = Mock() + mock_program_entry = Mock() + mock_jaqcd_entry = Mock() + mock_circuit_dm_entry = Mock() + mock_circuit_entry.load.return_value = DummyCircuitSimulator + mock_program_entry.load.return_value = DummyProgramSimulator + mock_jaqcd_entry.load.return_value = DummyJaqcdSimulator + mock_circuit_dm_entry.load.return_value = DummyProgramDensityMatrixSimulator + local_simulator._simulator_devices = { + "dummy": mock_circuit_entry, + "dummy_oq3": mock_program_entry, + "dummy_jaqcd": mock_jaqcd_entry, + "dummy_oq3_dm": mock_circuit_dm_entry, + } + return local_simulator._simulator_devices + mock_ahs_program = AnalogHamiltonianSimulation( register=AtomArrangement(), hamiltonian=Hamiltonian() diff --git a/test/unit_tests/braket/emulation/conftest.py b/test/unit_tests/braket/emulation/conftest.py new file mode 100644 index 000000000..6e99dfe3e --- /dev/null +++ b/test/unit_tests/braket/emulation/conftest.py @@ -0,0 +1,488 @@ +# 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. + +import json +import numpy as np +import pytest + +from braket.circuits import Circuit +from braket.device_schema.standardized_gate_model_qpu_device_properties_v1 import ( + CoherenceTime, + Fidelity1Q, + FidelityType, + OneQubitProperties, + TwoQubitProperties, + GateFidelity2Q, +) +from braket.device_schema.ionq.ionq_device_capabilities_v1 import IonqDeviceCapabilities + +################################################# +# Common test data for device property fixtures +################################################# + + +def create_one_qubit_properties(fidelity_types): + """ + Create OneQubitProperties with specified fidelity types. + + Args: + fidelity_types (list): List of fidelity type names to include + + Returns: + dict: OneQubitProperties as a dictionary + """ + fidelities = [] + + for fidelity_type in fidelity_types: + fidelities.append( + Fidelity1Q( + fidelityType=FidelityType(name=fidelity_type, description=None), + fidelity=0.99 if fidelity_type != "READOUT" else 0.9795, + standardError=None, + ) + ) + + return OneQubitProperties( + T1=CoherenceTime(value=2e-5, standardError=None, unit="S"), + T2=CoherenceTime(value=8e-6, standardError=None, unit="S"), + oneQubitFidelity=fidelities, + ).dict() + + +def create_two_qubit_properties(gate_names): + """ + Create TwoQubitProperties with specified gate names. + + Args: + gate_names (list): List of gate names to include + + Returns: + dict: TwoQubitProperties as a dictionary + """ + gate_fidelities = [] + + for gate_name in gate_names: + gate_fidelities.append( + GateFidelity2Q( + direction=None, + gateName=gate_name, + fidelity=0.99, + standardError=0.0009, + fidelityType=FidelityType(name="RANDOMIZED_BENCHMARKING", description=None), + ) + ) + + return TwoQubitProperties(twoQubitGateFidelity=gate_fidelities).dict() + + +# Valid one-qubit properties with RANDOMIZED_BENCHMARKING fidelity +valid_oneQubitProperties = create_one_qubit_properties(["RANDOMIZED_BENCHMARKING", "READOUT"]) + +# Valid one-qubit properties with SIMULTANEOUS_RANDOMIZED_BENCHMARKING fidelity +valid_oneQubitProperties_v2 = create_one_qubit_properties([ + "SIMULTANEOUS_RANDOMIZED_BENCHMARKING", + "READOUT", +]) + +# Valid two-qubit properties with CZ and ISwap gates +valid_twoQubitProperties = create_two_qubit_properties(["CZ", "ISwap"]) + +# Common supported result types +valid_supportedResultTypes = [ + {"maxShots": 20000, "minShots": 1, "name": "Probability", "observables": None} +] + +# Common native gate sets +valid_nativeGateSet = ["cz", "prx"] + +################################################# +# Invalid device properties for testing +################################################# + +# Invalid one-qubit properties without 1q rb data +invalid_oneQubitProperties = create_one_qubit_properties(["READOUT"]) + +# Invalid two-qubit properties without valid Braket gates +invalid_twoQubitProperties = create_two_qubit_properties(["not_a_braket_gate"]) + +# Invalid device properties with missing one-qubit RB data +invalid_device_properties_dict_1 = { + "braketSchemaHeader": { + "name": "braket.device_schema.iqm.iqm_device_capabilities", + "version": "1", + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + "updatedAt": "2024-04-04T01:10:02.869136", + }, + "deviceParameters": {}, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1.0"], + } + }, + "paradigm": { + "connectivity": {"connectivityGraph": {}, "fullyConnected": False}, + "nativeGateSet": valid_nativeGateSet, + "qubitCount": 2, + }, + "standardized": { + "oneQubitProperties": { + "0": invalid_oneQubitProperties, + "1": valid_oneQubitProperties, + }, + "twoQubitProperties": { + "0-1": valid_twoQubitProperties, + }, + }, +} + +# Invalid device properties with invalid two-qubit gate names +invalid_device_properties_dict_2 = { + "braketSchemaHeader": { + "name": "braket.device_schema.iqm.iqm_device_capabilities", + "version": "1", + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + "updatedAt": "2024-04-04T01:10:02.869136", + }, + "deviceParameters": {}, + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1.0"], + } + }, + "paradigm": { + "connectivity": {"connectivityGraph": {}, "fullyConnected": False}, + "nativeGateSet": valid_nativeGateSet, + "qubitCount": 2, + }, + "standardized": { + "oneQubitProperties": { + "0": valid_oneQubitProperties, + "1": valid_oneQubitProperties, + }, + "twoQubitProperties": { + "0-1": invalid_twoQubitProperties, + }, + }, +} + +################################################# +# IQM device property fixtures +################################################# + +# IQM device properties with simple connectivity +reduced_standardized_gate_model_qpu_device_properties_dict = { + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1"], + } + }, + "braketSchemaHeader": { + "name": "braket.device_schema.iqm.iqm_device_capabilities", + "version": "1", + }, + "deviceParameters": {}, + "paradigm": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_qpu_paradigm_properties", + "version": "1", + }, + "connectivity": {"connectivityGraph": {"0": ["1"]}, "fullyConnected": False}, + "nativeGateSet": ["cz", "prx"], + "qubitCount": 2, + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + "updatedAt": "2024-04-04T01:10:02.869136", + }, + "standardized": { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": valid_oneQubitProperties, + "1": valid_oneQubitProperties_v2, + }, + "twoQubitProperties": {"0-1": valid_twoQubitProperties}, + }, +} + +# IQM device properties with directed connectivity +reduced_standardized_gate_model_qpu_device_properties_dict_non_fully_connected_directed = { + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1"], + } + }, + "braketSchemaHeader": { + "name": "braket.device_schema.iqm.iqm_device_capabilities", + "version": "1", + }, + "deviceParameters": {}, + "paradigm": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_qpu_paradigm_properties", + "version": "1", + }, + "connectivity": {"connectivityGraph": {"0": ["1"], "1": ["2"]}, "fullyConnected": False}, + "nativeGateSet": ["cz", "prx"], + "qubitCount": 3, + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + }, + "standardized": { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": valid_oneQubitProperties, + "1": valid_oneQubitProperties, + "2": valid_oneQubitProperties, + }, + "twoQubitProperties": { + "0-1": valid_twoQubitProperties, + "1-2": valid_twoQubitProperties, + }, + }, +} + + +@pytest.fixture +def reduced_standardized_json(): + """ + Fixture providing a JSON string of IQM device properties with simple connectivity. + """ + return json.dumps(reduced_standardized_gate_model_qpu_device_properties_dict) + + +@pytest.fixture +def reduced_standardized_json_3(): + """ + Fixture providing a JSON string of IQM device properties with directed connectivity. + """ + return json.dumps( + reduced_standardized_gate_model_qpu_device_properties_dict_non_fully_connected_directed + ) + + +################################################# +# Rigetti device property fixtures +################################################# + +# Rigetti device properties with undirected connectivity +reduced_standardized_gate_model_qpu_device_properties_dict_non_fully_connected_undirected = { + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1"], + } + }, + "braketSchemaHeader": { + "name": "braket.device_schema.rigetti.rigetti_device_capabilities", + "version": "1", + }, + "deviceParameters": {}, + "paradigm": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_qpu_paradigm_properties", + "version": "1", + }, + "connectivity": { + "connectivityGraph": {"0": ["1"], "1": ["0", "2"], "2": ["1"]}, + "fullyConnected": False, + }, + "nativeGateSet": ["rx", "rz", "iswap"], + "qubitCount": 3, + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + }, + "standardized": { + "braketSchemaHeader": { + "name": "braket.device_schema.standardized_gate_model_qpu_device_properties", + "version": "1", + }, + "oneQubitProperties": { + "0": valid_oneQubitProperties, + "1": valid_oneQubitProperties, + "2": valid_oneQubitProperties, + }, + "twoQubitProperties": {"0-1": valid_twoQubitProperties, "1-2": valid_twoQubitProperties}, + }, +} + + +@pytest.fixture +def reduced_standardized_json_2(): + """ + Fixture providing a JSON string of Rigetti device properties with undirected connectivity. + """ + return json.dumps( + reduced_standardized_gate_model_qpu_device_properties_dict_non_fully_connected_undirected + ) + + +################################################# +# IonQ device property fixtures +################################################# + +# IonQ device properties with fully connected topology +reduced_ionq_device_capabilities_dict = { + "action": { + "braket.ir.openqasm.program": { + "actionType": "braket.ir.openqasm.program", + "supportedOperations": [], + "supportedResultTypes": valid_supportedResultTypes, + "version": ["1"], + } + }, + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_device_capabilities", + "version": "1", + }, + "deviceParameters": {}, + "paradigm": { + "braketSchemaHeader": { + "name": "braket.device_schema.gate_model_qpu_paradigm_properties", + "version": "1", + }, + "connectivity": {"connectivityGraph": {}, "fullyConnected": True}, + "nativeGateSet": ["GPI", "GPI2", "MS"], + "qubitCount": 25, + }, + "provider": { + "braketSchemaHeader": { + "name": "braket.device_schema.ionq.ionq_provider_properties", + "version": "1", + }, + "errorMitigation": { + "braket.device_schema.error_mitigation.debias.Debias": {"minimumShots": 2500} + }, + "fidelity": {"1Q": {"mean": 0.9998}, "2Q": {"mean": 0.12345}, "spam": {"mean": 0.9937}}, + "timing": { + "1Q": 0.000135, + "2Q": 0.0006, + "T1": 123.45678, + "T2": 1.0, + "readout": 0.0003, + "reset": 2e-05, + }, + }, + "service": { + "braketSchemaHeader": { + "name": "braket.device_schema.device_service_properties", + "version": "1", + }, + "executionWindows": [], + "shotsRange": [1, 20000], + }, +} + + +@pytest.fixture +def reduced_ionq_device_capabilities_json(): + """ + Fixture providing a JSON string of IonQ device properties. + """ + return json.dumps(reduced_ionq_device_capabilities_dict) + + +@pytest.fixture +def reduced_ionq_device_capabilities(reduced_ionq_device_capabilities_json): + """ + Fixture providing an IonqDeviceCapabilities object. + """ + return IonqDeviceCapabilities.parse_raw(reduced_ionq_device_capabilities_json) + + +################################################# +# Circuit fixtures +################################################# + + +@pytest.fixture +def valid_verbatim_circ_garnet(): + """ + Fixture providing a valid verbatim circuit for Garnet device. + """ + return Circuit().add_verbatim_box(Circuit().prx(1, 0, 0).cz(1, 2).prx(2, np.pi, 0).cz(0, 1)) + + +@pytest.fixture +def valid_verbatim_circ_ankaa3(): + """ + Fixture providing a valid verbatim circuit for Ankaa-3 device. + """ + return Circuit().add_verbatim_box( + Circuit().rx(0, np.pi).rz(1, np.pi).iswap(0, 1).iswap(1, 2).rx(2, np.pi) + ) + + +@pytest.fixture +def valid_verbatim_circ_aria1(): + """ + Fixture providing a valid verbatim circuit for Aria-1 device. + """ + return Circuit().add_verbatim_box( + Circuit() + .gpi(0, 3.14) + .gpi2(1, 3.14) + .ms(0, 1, 3.1, 3.2, 3.3) + .ms(1, 2, 3.1, 3.3, 4.3) + .gpi(2, 3.14) + ) diff --git a/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py b/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py new file mode 100644 index 000000000..1f37f0be3 --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py @@ -0,0 +1,210 @@ +import networkx as nx +import numpy as np +import pytest +from networkx.utils import graphs_equal + +from braket.circuits import Circuit +from braket.emulation.passes.circuit_passes import ConnectivityValidator + + +@pytest.fixture +def basic_2_node_complete_graph(): + return nx.complete_graph(2, create_using=nx.DiGraph()) + + +@pytest.fixture +def basic_noncontig_qubits_2_node_complete_graph(): + return nx.complete_graph([1, 10], create_using=nx.DiGraph()) + + +@pytest.fixture +def six_node_digraph(): + edge_set = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} + return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box(Circuit()), + Circuit().i(range(2)).cnot(3, 4), + Circuit().add_verbatim_box(Circuit().h(0).h(1).cnot(0, 1).cnot(1, 0)), + Circuit() + .h(range(2)) + .add_verbatim_box( + Circuit().swap(0, 1).phaseshift(1, np.pi / 4).cphaseshift01(1, 0, np.pi / 4) + ), + ], +) +def test_basic_contiguous_circuits(basic_2_node_complete_graph, circuit): + """ + ConnectivityValidator should not raise any errors when validating these circuits. + """ + ConnectivityValidator(basic_2_node_complete_graph).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box(Circuit()), + Circuit().i(range(3)).cnot(3, 4).x(111), + Circuit().add_verbatim_box(Circuit().h(1).h(10).cnot(1, 10).cnot(10, 1)), + Circuit().add_verbatim_box( + Circuit().swap(1, 10).phaseshift(10, np.pi / 4).cphaseshift01(10, 1, np.pi / 4) + ), + ], +) +def test_valid_discontiguous_circuits(basic_noncontig_qubits_2_node_complete_graph, circuit): + """ + ConnectivityValidator should not raise any errors when validating these circuits. + """ + ConnectivityValidator(basic_noncontig_qubits_2_node_complete_graph).validate(circuit) + + +def test_complete_graph_instantation_with_num_qubits(): + """ + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityValidator constructor, a fully connected graph is created. + """ + num_qubits = 5 + validator = ConnectivityValidator(num_qubits=num_qubits, fully_connected=True) + vb = Circuit() + for i in range(num_qubits): + for j in range(num_qubits): + if i != j: + vb.cnot(i, j) + else: + vb.i(i) + circuit = Circuit().add_verbatim_box(vb) + validator.validate(circuit) + assert nx.utils.graphs_equal( + validator._connectivity_graph, nx.complete_graph(num_qubits, create_using=nx.DiGraph()) + ) + + +def test_complete_graph_instantation_with_qubit_labels(): + """ + Tests that, if fully_connected is True and num_qubits are passed into the + ConnectivityValidator constructor, a fully connected graph is created. + """ + qubit_labels = [0, 1, 10, 11, 110, 111, 112, 113] + validator = ConnectivityValidator(qubit_labels=qubit_labels, fully_connected=True) + vb = Circuit() + for i in qubit_labels: + for j in qubit_labels: + if i != j: + vb.cnot(i, j) + else: + vb.i(i) + circuit = Circuit().add_verbatim_box(vb) + validator.validate(circuit) + assert nx.utils.graphs_equal( + validator._connectivity_graph, nx.complete_graph(qubit_labels, create_using=nx.DiGraph()) + ) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().add_verbatim_box(Circuit().cnot(0, 2)), + Circuit().add_verbatim_box(Circuit().swap(1, 3)), + Circuit() + .add_verbatim_box(Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi / 4)) + .x(4) + .add_verbatim_box(Circuit().swap(2, 10)), + ], +) +def test_invalid_2_qubit_gates(six_node_digraph, circuit): + with pytest.raises(ValueError): + ConnectivityValidator(six_node_digraph).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().x(4).add_verbatim_box(Circuit().x(4)), + Circuit().x(110).add_verbatim_box(Circuit().phaseshift(4, np.pi / 4)), + Circuit() + .add_verbatim_box(Circuit().cnot(1, 10).cphaseshift01(2, 11, np.pi / 4)) + .x(4) + .add_verbatim_box(Circuit().h(111)), + ], +) +def test_invalid_1_qubit_gates(six_node_digraph, circuit): + with pytest.raises(ValueError): + ConnectivityValidator(six_node_digraph).validate(circuit) + + +@pytest.mark.parametrize( + "connectivity_graph, fully_connected, num_qubits, qubit_labels, directed", + [ + (None, True, None, None, False), + (nx.DiGraph(), True, None, None, False), + (None, True, 5, [0, 1], False), + (None, False, None, None, False), + (nx.from_edgelist([(0, 1)], create_using=nx.Graph()), False, None, None, False), + ], +) +def test_invalid_constructors( + connectivity_graph, fully_connected, num_qubits, qubit_labels, directed +): + with pytest.raises(ValueError): + ConnectivityValidator( + connectivity_graph, fully_connected, num_qubits, qubit_labels, directed + ) + + +@pytest.mark.parametrize( + "representation", + [ + {1: [0, 2, 3], 2: [3, 4], 3: [6]}, + nx.from_edgelist([(1, 0), (1, 2), (1, 3), (2, 3), (2, 4), (3, 6)], create_using=nx.DiGraph), + ], +) +def test_undirected_graph_construction(representation): + expected_digraph = nx.from_edgelist( + [ + (1, 0), + (0, 1), + (1, 2), + (2, 1), + (1, 3), + (3, 1), + (2, 3), + (3, 2), + (2, 4), + (4, 2), + (3, 6), + (6, 3), + ], + create_using=nx.DiGraph, + ) + cc = ConnectivityValidator(representation, directed=False) + assert graphs_equal(cc._connectivity_graph, expected_digraph) + + +# @pytest.fixture +# def six_node_digraph(): +# edge_set = {0: [1, 3], 1: [0, 2, 10], 2: [1, 3, 11], 10: [1, 11], 11: [2, 10]} +# return nx.from_dict_of_lists(edge_set, create_using=nx.DiGraph()) + + +@pytest.mark.parametrize( + "controls,targets,is_valid", + [ + ([0], [1], True), + ([], [0, 1], True), + ([3], [0], True), + ([0, 2], [], False), + ([0], [1, 2], False), + ], +) +def test_validate_instruction_method(controls, targets, is_valid, six_node_digraph): + gcc = ConnectivityValidator(six_node_digraph, directed=False) + if is_valid: + gcc._validate_instruction_connectivity(controls, targets) + else: + with pytest.raises(ValueError): + gcc._validate_instruction_connectivity(controls, targets) diff --git a/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py b/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py new file mode 100644 index 000000000..956bb3321 --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py @@ -0,0 +1,267 @@ +import networkx as nx +import numpy as np +import pytest +from networkx.utils import graphs_equal + +from braket.circuits import Circuit, Gate, Instruction +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.noises import BitFlip +from braket.emulation.passes.circuit_passes import GateConnectivityValidator + + +@pytest.fixture +def basic_4_node_graph(): + G = nx.DiGraph() + G.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot"]}), + (0, 3, {"supported_gates": ["XX", "XY"]}), + ]) + return G + + +@pytest.fixture +def basic_discontiguous_4_node_graph(): + G = nx.DiGraph() + G.add_edges_from([ + (0, 5, {"supported_gates": ["CNot", "CZ"]}), + (2, 4, {"supported_gates": ["Swap", "CNot"]}), + (3, 0, {"supported_gates": ["XX", "XY"]}), + ]) + return G + + +@pytest.fixture +def basic_4_node_graph_as_dict(): + return { + (0, 1): ["CNot", "Swap", "CX", "XX"], + (1, 2): ["CNot", "CZ", "ISwap", "XY"], + (0, 3): ["PSwap", "CNot", "XY"], + } + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box(Circuit().cnot(0, 1).h(range(4))), + Circuit() + .add_verbatim_box(Circuit().cnot(0, 1).cz(0, 1).swap(1, 2).xx(0, 3, np.pi / 2)) + .add_verbatim_box(Circuit().xy(0, 3, np.pi / 2).cnot(1, 2).cz(0, 1)), + Circuit() + .i(range(10)) + .cnot(0, 2) + .yy(8, 9, np.pi / 2) + .h(7) + .add_verbatim_box(Circuit().cnot(0, 1).cz(0, 1)) + .cnot(0, 2) + .swap(4, 6), + Circuit().add_verbatim_box( + Circuit().h(0).apply_gate_noise(BitFlip(0.1), target_gates=Gate.H) + ), + ], +) +def test_valid_basic_contiguous_circuits(basic_4_node_graph, circuit): + """ + GateConnectivityValidator should not raise any errors when validating these circuits. + """ + gate_connectivity_validator = GateConnectivityValidator(basic_4_node_graph) + gate_connectivity_validator.validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box(Circuit().cnot(0, 5)), + Circuit() + .add_verbatim_box(Circuit().cnot(2, 4).cz(0, 5).swap(2, 4).xx(3, 0, np.pi / 2)) + .add_verbatim_box(Circuit().xy(3, 0, np.pi / 2).cnot(2, 4).cz(0, 5)), + Circuit() + .i(range(10)) + .cnot(0, 2) + .yy(8, 9, np.pi / 2) + .h(7) + .add_verbatim_box(Circuit().cnot(0, 5).swap(2, 4)) + .cnot(0, 2) + .swap(4, 6), + ], +) +def test_valid_basic_discontiguous_circuits(basic_discontiguous_4_node_graph, circuit): + """ + GateConnectivityValidator should not raise any errors when validating these circuits. + """ + gate_connectivity_validator = GateConnectivityValidator(basic_discontiguous_4_node_graph) + gate_connectivity_validator.validate(circuit) + + +def test_directed_graph_construction_from_dict(): + """ + GateConnectivityValidator should correctly construct a graph from a dictionary + representation of the connectivity. + """ + dict_representation = { + (0, 1): ["CNot", "CZ"], + (1, 2): ["Swap", "CNot", "YY"], + (0, 2): ["XX", "XY", "CNot", "CZ"], + (2, 5): ["XX", "XY", "CNot", "CZ"], + } + digraph_representation = nx.DiGraph() + digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + ]) + gcc = GateConnectivityValidator(dict_representation) + assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().add_verbatim_box(Circuit().cnot(0, 1)), + Circuit() + .swap(0, 1) + .add_verbatim_box(Circuit().iswap(2, 1).pswap(3, 0, np.pi / 2).pswap(0, 3, np.pi / 2)), + Circuit() + .cnot(2, 3) + .h(5) + .pswap(0, 6, np.pi / 2) + .add_verbatim_box(Circuit().xy(2, 1, np.pi / 2).cnot(0, 1).cnot(1, 0)) + .add_verbatim_box(Circuit().cnot(0, 3).cnot(3, 0).xy(0, 3, np.pi / 2)), + ], +) +def test_undirected_criteria_from_dict_with_valid_circuits(basic_4_node_graph_as_dict, circuit): + """ + GateConnectivityValidator should not raise any errors when validating these circuits. + """ + gate_connectivity_validator = GateConnectivityValidator( + basic_4_node_graph_as_dict, directed=False + ) + gate_connectivity_validator.validate(circuit) + + +def test_undirected_graph_construction_from_dict(): + """ + GateConnectivityValidator should correctly construct an undirected graph from a dictionary + representation of the connectivity. + """ + dict_representation = { + (0, 1): ["CNot", "CZ"], + (1, 0): ["CNot", "CZ"], + (1, 2): ["Swap", "CNot", "YY"], + (0, 2): ["XX", "XY", "CNot", "CZ"], + (2, 5): ["XX", "XY", "CNot", "CZ"], + } + digraph_representation = nx.DiGraph() + digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 2, {"supported_gates": ["Swap", "CNot", "YY"]}), + (0, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (2, 5, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CNot", "CZ"]}), + (2, 1, {"supported_gates": ["Swap", "CNot", "YY"]}), + (2, 0, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + (5, 2, {"supported_gates": ["XX", "XY", "CNot", "CZ"]}), + ]) + gcc = GateConnectivityValidator(dict_representation, directed=False) + assert graphs_equal(gcc._gate_connectivity_graph, digraph_representation) + + +@pytest.mark.parametrize( + "representation", + [ + {(0, 1): ["CNot", "CZ"], (1, 0): ["CZ, XX"], (2, 0): ["CNot, YY"]}, + nx.from_dict_of_dicts({ + 0: {1: {"supported_gates": ["CNot", "CZ"]}}, + 1: {0: {"supported_gates": ["CZ", "XX"]}}, + 2: {2: {"supported_gates": ["CNot", "YY"]}}, + }), + ], +) +def create_undirected_graph_with_exisiting_back_edges(representation): + """ + Check that creating an undirected graph with a graph that + contains forwards and backwards edges with different constraints + is created properly. + """ + + gcc = GateConnectivityValidator(representation, directed=False) + expected_digraph_representation = nx.DiGraph() + expected_digraph_representation.add_edges_from([ + (0, 1, {"supported_gates": ["CNot", "CZ"]}), + (1, 0, {"supported_gates": ["CZ", "XX"]}), + (2, 0, {"supported_gates": ["CNot", "YY"]}), + (0, 2, {"supported_gates": ["CNot", "YY"]}), + ]) + + assert graphs_equal(gcc._gate_connectivity_graph, expected_digraph_representation) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().add_verbatim_box(Circuit().cnot(1, 0)), + Circuit().add_verbatim_box(Circuit().h(4)), + Circuit().add_verbatim_box(Circuit().swap(1, 2).xx(0, 3, np.pi / 2).iswap(0, 1)), + Circuit().add_verbatim_box(Circuit().cnot(0, 3)), + Circuit().add_instruction(Instruction(StartVerbatimBox())), + Circuit() + .add_instruction(Instruction(StartVerbatimBox())) + .add_instruction(Instruction(StartVerbatimBox())), + Circuit().add_instruction(Instruction(EndVerbatimBox())), + ], +) +def test_invalid_circuits(basic_4_node_graph, circuit): + with pytest.raises(ValueError): + gate_connectivity_validator = GateConnectivityValidator(basic_4_node_graph) + gate_connectivity_validator.validate(circuit) + + +def test_invalid_connectivity_graph(): + bad_graph = nx.complete_graph(5, create_using=nx.Graph()) + with pytest.raises(TypeError): + GateConnectivityValidator(bad_graph) + + +@pytest.mark.parametrize( + "gate_name,controls,targets,is_valid", + [ + ("CZ", [0], [1], True), + ("CNot", [], [0, 1], True), + ("XY", [3], [0], True), + ("CZ", [0, 2], [], False), + ("Swap", [0], [1, 2], False), + ("ZZ", [3], [0], False), + ], +) +def test_validate_instruction_method(gate_name, controls, targets, is_valid, basic_4_node_graph): + gcc = GateConnectivityValidator(basic_4_node_graph, directed=False) + if is_valid: + gcc._validate_instruction_connectivity(gate_name, controls, targets) + else: + with pytest.raises(ValueError): + gcc._validate_instruction_connectivity(gate_name, controls, targets) + + +@pytest.mark.parametrize( + "graph", + [ + ( + nx.from_dict_of_dicts( + { + 0: {1: {"supported_gates": ["cnot", "cz"]}}, + 1: {0: {"supported_gates": ["cz", "cnot", "xx"]}}, + }, + create_using=nx.DiGraph(), + ) + ), + ({(0, 1): ["cnot", "cz"], (1, 0): ["cz", "cnot", "xx"]}), + ({(0, 1): ["xx", "yy"], (1, 0): ["yy", "xx"]}), + ], +) +def test_invalid_undirected_graph(graph): + with pytest.raises(ValueError): + GateConnectivityValidator(graph, directed=False) diff --git a/test/unit_tests/braket/emulation/passes/test_gate_validator.py b/test/unit_tests/braket/emulation/passes/test_gate_validator.py new file mode 100644 index 000000000..39ff8276c --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_gate_validator.py @@ -0,0 +1,157 @@ +import numpy as np +import pytest + +from braket.circuits import Circuit, Gate, Instruction +from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox +from braket.circuits.noises import BitFlip +from braket.emulation.passes.circuit_passes import GateValidator + + +@pytest.fixture +def basic_gate_set(): + return (["h", "cnot"], ["cz", "prx"]) + + +@pytest.fixture +def mock_qpu_gates(): + supported_gates = [ + "ccnot", + "cnot", + "cphaseshift", + "cswap", + "swap", + "iswap", + "pswap", + "ecr", + "cy", + "cz", + "xy", + "zz", + "h", + "i", + "phaseshift", + "rx", + "ry", + "v", + "vi", + "x", + "y", + "z", + ] + + native_gates = ["cz", "prx"] + return (supported_gates, native_gates) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit() + .h(range(4)) + .cnot(0, 5) + .pswap(0, 1, np.pi / 4) + .xy(0, 1, 0.5) + .cphaseshift(0, 1, np.pi / 4), + Circuit() + .swap(0, 1) + .rx(0, 0.5) + .v(1) + .h(2) + .add_verbatim_box(Circuit().cz(0, 1).cz(0, 6).prx(0, np.pi / 4, np.pi / 5)) + .z(3), + Circuit() + .add_verbatim_box(Circuit().cz(0, 2).prx(0, 0.5, 0.5)) + .add_verbatim_box(Circuit().cz(0, 4).cz(3, 6)), + Circuit().h(0).add_verbatim_box(Circuit()), + Circuit() + .add_verbatim_box( + Circuit().prx(0, np.pi / 4, np.pi / 4).apply_gate_noise(BitFlip(0.1), Gate.PRx) + ) + .v(1) + .apply_gate_noise(BitFlip(0.1), Gate.V), + ], +) +def test_valid_circuits(mock_qpu_gates, circuit): + """ + GateValidator should not raise any errors when validating these circuits. + """ + GateValidator(mock_qpu_gates[0], mock_qpu_gates[1]).validate(circuit) + + +def test_only_supported_gates(): + supported_gates = ["h", "cnot", "rx", "xx", "y"] + validator = GateValidator(supported_gates=supported_gates) + circuit = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) + validator.validate(circuit) + + +def test_verbatim_circuit_only_supported_gates(): + supported_gates = ["h", "cnot", "rx", "xx", "y"] + validator = GateValidator(supported_gates=supported_gates) + circuit = Circuit().add_verbatim_box(Circuit().h(0)) + + with pytest.raises(ValueError): + validator.validate(circuit) + + +def test_only_native_gates(): + native_gates = ["h", "cnot", "rx", "xx", "y"] + validator = GateValidator(native_gates=native_gates) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) + circuit = Circuit().add_verbatim_box(vb) + validator.validate(circuit) + + +def test_non_verbatim_circuit_only_native_gates(): + native_gates = ["h", "cnot", "rx", "xx", "y"] + validator = GateValidator(native_gates=native_gates) + vb = Circuit().h(0).cnot(0, 1).rx(4, np.pi / 4).xx(2, 3, np.pi / 4).y(7) + circuit = Circuit().add_verbatim_box(vb) + circuit.i(0) + with pytest.raises(ValueError): + validator.validate(circuit) + + +@pytest.mark.parametrize( + "supported_gates,native_gates,error_message", + [ + ([], [], "Supported gate set or native gate set must be provided."), + (["CX"], [], "Input 'cx' in supported_gates is not a valid Braket gate name."), + ([], ["CX"], "Input 'cx' in native_gates is not a valid Braket gate name."), + ( + ["Toffoli"], + ["CX"], + "Input 'toffoli' in supported_gates is not a valid Braket gate name.", + ), + ], +) +def test_invalid_instantiation(supported_gates, native_gates, error_message): + with pytest.raises(ValueError, match=error_message): + GateValidator(supported_gates, native_gates) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().z(0), + Circuit().h(0).cnot(0, 1).cz(0, 1), + Circuit().add_verbatim_box(Circuit().h(2)), + Circuit().cphaseshift01(0, 1, np.pi / 4).h(0).cnot(0, 1), + Circuit() + .h(0) + .add_verbatim_box(Circuit().cz(1, 2).prx(range(5), np.pi / 4, np.pi / 2).cz(2, 6)) + .prx(range(4), np.pi / 4, np.pi / 6), + Circuit().add_instruction(Instruction(StartVerbatimBox())), + Circuit() + .add_instruction(Instruction(StartVerbatimBox())) + .add_instruction(Instruction(StartVerbatimBox())), + Circuit().add_instruction(Instruction(EndVerbatimBox())), + ], +) +def test_invalid_circuits(basic_gate_set, circuit): + """ + GateValidator should raise errors when validating these circuits. + """ + with pytest.raises(ValueError): + GateValidator(basic_gate_set[0], basic_gate_set[1]).validate(circuit) diff --git a/test/unit_tests/braket/emulation/passes/test_not_implemented_validator.py b/test/unit_tests/braket/emulation/passes/test_not_implemented_validator.py new file mode 100644 index 000000000..2d0a988d0 --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_not_implemented_validator.py @@ -0,0 +1,46 @@ +# 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. + +import pytest + +from braket.circuits import Circuit +from braket.emulation.passes.circuit_passes import _NotImplementedValidator +from braket.program_sets import ProgramSet + + +@pytest.fixture +def default_not_implemented_validator(): + return _NotImplementedValidator() + + +def test_validate_circuit_with_verbatim_box(default_not_implemented_validator): + """Test that a circuit with a verbatim box passes validation.""" + circuit = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1)) + # Should not raise an exception + default_not_implemented_validator.validate(circuit) + + +def test_validate_circuit_without_verbatim_box(default_not_implemented_validator): + """Test that a circuit without a verbatim box fails validation.""" + circuit = Circuit().h(0).cnot(0, 1) + with pytest.raises( + ValueError, + match="The input circuit must have a verbatim box. Add a verbatim box to the circuit, and try again.", + ): + default_not_implemented_validator.validate(circuit) + + +def test_program_set(default_not_implemented_validator): + program_set = ProgramSet([Circuit().h(0).cnot(0, 1), Circuit().rx(0, 0)]) + with pytest.raises(TypeError): + default_not_implemented_validator.validate(program_set) diff --git a/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py b/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py new file mode 100644 index 000000000..c7c7cf878 --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py @@ -0,0 +1,47 @@ +import numpy as np +import pytest + +from braket.circuits import Circuit +from braket.emulation.passes.circuit_passes import QubitCountValidator + + +@pytest.mark.parametrize( + "qubit_count,circuit", + [ + (1, Circuit()), + (10, Circuit().add_verbatim_box(Circuit())), + (1, Circuit().z(0)), + (1, Circuit().z(3).x(3)), + (2, Circuit().cnot(0, 1).swap(1, 0)), + (2, Circuit().z(0).add_verbatim_box(Circuit().cnot(0, 4)).yy(0, 4, np.pi / 4)), + (50, Circuit().i(range(50)).measure(range(50))), + ], +) +def test_valid_circuits(qubit_count, circuit): + """ + QubitCountValidator should not raise any errors when validating these circuits. + """ + QubitCountValidator(qubit_count=qubit_count).__call__(circuit) + + +@pytest.mark.parametrize("qubit_count", [0, -1]) +def test_invalid_instantiation(qubit_count): + with pytest.raises(ValueError): + QubitCountValidator(qubit_count) + + +@pytest.mark.parametrize( + "qubit_count,circuit", + [ + (1, Circuit().cnot(0, 1)), + (2, Circuit().cnot(0, 1).x(2)), + (50, Circuit().i(range(50)).measure(range(50)).measure(50)), + ], +) +def test_invalid_circuits(qubit_count, circuit): + with pytest.raises( + ValueError, + match=f"Circuit must use at most {qubit_count} qubits, \ +but uses {circuit.qubit_count} qubits.", + ): + QubitCountValidator(qubit_count).validate(circuit) diff --git a/test/unit_tests/braket/emulation/passes/test_result_type_validator.py b/test/unit_tests/braket/emulation/passes/test_result_type_validator.py new file mode 100644 index 000000000..8087e46ec --- /dev/null +++ b/test/unit_tests/braket/emulation/passes/test_result_type_validator.py @@ -0,0 +1,102 @@ +import pytest +import re +from braket.circuits import Circuit, Observable +from braket.circuits.observables import X, Y, Z, H, I +from braket.device_schema.result_type import ResultType +from braket.emulation.passes.circuit_passes.result_type_validator import ResultTypeValidator + + +@pytest.fixture +def supported_result_types(): + names = ["Sample", "Expectation", "Variance", "Probability"] + observables = ["x", "y", "z", "h", "i"] + return [ResultType(name=name, observables=observables) for name in names] + + +@pytest.fixture +def connectivity_graph(): + # Create a simple connectivity graph for a 2-qubit device + return {"0": ["1"], "1": ["0"]} + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit(), + Circuit().h(0).cnot(0, 1).probability(), + Circuit().h(0).cnot(0, 1).expectation(observable=Z(), target=0), + Circuit().h(0).cnot(0, 1).sample(observable=X(), target=0), + Circuit().h(0).cnot(0, 1).variance(observable=Z(), target=0), + Circuit().h(0).cnot(0, 1).expectation(observable=Z(), target=0).probability(), + Circuit().h(0).cnot(0, 1).sample(observable=Y(), target=1).probability(target=[0]), + Circuit().h(0).cnot(0, 1).variance(observable=H(), target=0), + Circuit().h(0).cnot(0, 1).expectation(observable=I(), target=0), + ], +) +def test_valid_circuits(supported_result_types, connectivity_graph, circuit): + """ + ResultTypeValidator should not raise any errors when validating these circuits. + """ + ResultTypeValidator(supported_result_types, connectivity_graph).validate(circuit) + + +@pytest.mark.parametrize( + "circuit", + [ + Circuit().h(0).cnot(0, 1).state_vector(), + Circuit().h(0).cnot(0, 1).probability().state_vector(), + Circuit().h(0).cnot(0, 1).expectation(observable=Z(), target=0).state_vector(), + Circuit().h(0).cnot(0, 1).density_matrix(), + Circuit().h(0).cnot(0, 1).amplitude(state=["00", "11"]), + ], +) +def test_invalid_circuits(supported_result_types, connectivity_graph, circuit): + """ + ResultTypeValidator should raise errors when validating these circuits. + """ + with pytest.raises(ValueError): + ResultTypeValidator(supported_result_types, connectivity_graph).validate(circuit) + + +def test_invalid_instantiation(): + with pytest.raises(ValueError, match="Supported result types must be provided."): + ResultTypeValidator([], {"0": ["1"]}) + + with pytest.raises(ValueError, match="Connectivity graph must be provided."): + ResultTypeValidator(["Expectation"], None) + + +def test_invalid_qubit_target(): + """ + Test that ResultTypeValidator raises an error when a result type targets a qubit + that is not in the device's connectivity graph. + """ + # Create a connectivity graph for a 1-qubit device + connectivity_graph = {"0": []} + + # Create a circuit with a result type targeting qubit 1, which is not in the device + circuit = Circuit().h(0).expectation(observable=Z(), target=1) + + # The validator should raise an error because qubit 1 is not in the device + with pytest.raises( + ValueError, + match="Qubit 1 in result type Expectation is not a valid qubit for this device. The set of valid qubits can be found in the connectivity graph of the device.", + ): + ResultTypeValidator( + [ResultType(name="Expectation", observables=["x", "y", "z", "h", "i"])], + connectivity_graph, + ).validate(circuit) + + +def test_observables(supported_result_types, connectivity_graph): + circuit = Circuit().h(0).cnot(0, 1).sample(Observable.Z(0) @ Observable.Z(1)) + observable_name = circuit.result_types[0].observable.name.lower() + error_messasge = re.escape( + f"Observable {observable_name} is not supported for result type Sample on this device. " + f"Supported observables are: ['x', 'y', 'z', 'h', 'i']." + ) + with pytest.raises( + ValueError, + match=error_messasge, + ): + ResultTypeValidator(supported_result_types, connectivity_graph).validate(circuit) diff --git a/test/unit_tests/braket/emulation/test__standardization.py b/test/unit_tests/braket/emulation/test__standardization.py new file mode 100644 index 000000000..bbaaad36a --- /dev/null +++ b/test/unit_tests/braket/emulation/test__standardization.py @@ -0,0 +1,35 @@ +# 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. + +import pytest + +from braket.emulation.device_emulator_properties import ( + DeviceEmulatorProperties, +) + +from braket.emulation._standardization import ( + _standardize_ionq_device_properties, +) +from braket.device_schema.iqm.iqm_device_capabilities_v1 import IqmDeviceCapabilities + + +def test_standardize_ionq_device_properties(reduced_ionq_device_capabilities): + device_properties = _standardize_ionq_device_properties(reduced_ionq_device_capabilities) + device_em_properties = DeviceEmulatorProperties.from_device_properties(device_properties) + assert device_em_properties.qubit_count == 25 + + +def test_invalid_standardize_ionq_device_properties(reduced_standardized_json_3): + device_properties = IqmDeviceCapabilities.parse_raw(reduced_standardized_json_3) + with pytest.raises(ValueError): + _standardize_ionq_device_properties(device_properties) diff --git a/test/unit_tests/braket/emulation/test_device_emulator_properties.py b/test/unit_tests/braket/emulation/test_device_emulator_properties.py new file mode 100644 index 000000000..a587aaff3 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_device_emulator_properties.py @@ -0,0 +1,179 @@ +# 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. + +import pytest +import json + +from braket.emulation.device_emulator_properties import ( + DeviceEmulatorProperties, +) + +from braket.device_schema.iqm.iqm_device_capabilities_v1 import IqmDeviceCapabilities + +from conftest import ( + valid_oneQubitProperties, + valid_oneQubitProperties_v2, + valid_twoQubitProperties, + valid_supportedResultTypes, + valid_nativeGateSet, +) + + +def test_basic_instantiation(): + result = DeviceEmulatorProperties( + qubitCount=2, + nativeGateSet=["cz", "prx"], + connectivityGraph={"0": ["1"], "1": ["0"]}, + oneQubitProperties={"0": valid_oneQubitProperties, "1": valid_oneQubitProperties}, + twoQubitProperties={"0-1": valid_twoQubitProperties}, + supportedResultTypes=valid_supportedResultTypes, + ) + assert result.qubit_count == 2 + assert result.native_gate_set == ["cz", "prx"] + assert result.connectivity_graph == {"0": ["1"], "1": ["0"]} + assert ( + result.one_qubit_properties["0"] + == result.one_qubit_properties["1"] + == valid_oneQubitProperties + ) + assert result.two_qubit_properties["0-1"] == valid_twoQubitProperties + assert result.supported_result_types == valid_supportedResultTypes + assert result.qubit_labels == [0, 1] + assert result.fully_connected == True + assert result.directed == False + + +def test_from_json_3(reduced_standardized_json): + result = DeviceEmulatorProperties.from_json(reduced_standardized_json) + assert result.qubit_count == 2 + assert result.native_gate_set == valid_nativeGateSet + assert result.connectivity_graph == {"0": ["1"]} + assert ( + result.one_qubit_properties["0"] == valid_oneQubitProperties, + result.one_qubit_properties["1"] == valid_oneQubitProperties_v2, + ) + assert result.two_qubit_properties["0-1"] == valid_twoQubitProperties + assert result.supported_result_types == valid_supportedResultTypes + assert result.qubit_labels == [0, 1] + assert result.fully_connected == True + assert result.directed == True + + +def test_from_device_properties(reduced_standardized_json): + device_properties = IqmDeviceCapabilities.parse_raw(reduced_standardized_json) + result = DeviceEmulatorProperties.from_device_properties(device_properties) + assert result.qubit_count == 2 + assert result.native_gate_set == valid_nativeGateSet + assert result.connectivity_graph == {"0": ["1"]} + assert ( + result.one_qubit_properties["0"] == valid_oneQubitProperties, + result.one_qubit_properties["1"] == valid_oneQubitProperties_v2, + ) + assert result.two_qubit_properties["0-1"] == valid_twoQubitProperties + assert result.supported_result_types == valid_supportedResultTypes + assert result.qubit_labels == [0, 1] + + +@pytest.mark.parametrize( + "field, invalid_values", + [ + ("paradigm.nativeGateSet", ["not_a_Braket_gate"]), + ("paradigm.connectivity.connectivityGraph", {2: [0]}), + ("paradigm.connectivity.connectivityGraph", {0: [2]}), + ("standardized.oneQubitProperties", {"2": valid_oneQubitProperties}), + ("standardized.oneQubitProperties", {"0": valid_oneQubitProperties}), + ], +) +def test_invalid_device_emulator_properties(reduced_standardized_json, field, invalid_values): + with pytest.raises(ValueError): + minimal_invalid_json_dict = json.loads(reduced_standardized_json) + + # replace with invalid values + field_split = field.split(".") + pointer = minimal_invalid_json_dict + for k in field_split[:-1]: + pointer = pointer[k] + pointer[field_split[-1]] = invalid_values + + minimal_invalid_json_dict[field] = invalid_values + DeviceEmulatorProperties.from_json(json.dumps(minimal_invalid_json_dict)) + + +@pytest.mark.parametrize( + "invalid_device_properties", + [ + (1), + (valid_oneQubitProperties), + ], +) +def test_invalid_instantiation_from_invalid_device_properties(invalid_device_properties): + with pytest.raises(TypeError): + DeviceEmulatorProperties.from_device_properties(invalid_device_properties) + + +@pytest.mark.parametrize( + "missing_field", + [ + ("standardized"), + ("paradigm"), + ("braket.ir.openqasm.program"), + ], +) +def test_invalid_instantiation_due_to_missing_field(reduced_standardized_json, missing_field): + minimal_valid_dict = json.loads(reduced_standardized_json) + if missing_field == "braket.ir.openqasm.program": + minimal_valid_dict["action"].pop(missing_field) + else: + minimal_valid_dict.pop(missing_field) + with pytest.raises(ValueError): + DeviceEmulatorProperties.from_json(json.dumps(minimal_valid_dict)) + + +def test_from_json_non_fully_connected(reduced_standardized_json_2): + result = DeviceEmulatorProperties.from_json(reduced_standardized_json_2) + assert result.qubit_count == 3 + assert result.native_gate_set == ["rx", "rz", "iswap"] + assert result.connectivity_graph == {"0": ["1"], "1": ["0", "2"], "2": ["1"]} + assert ( + result.one_qubit_properties["2"] + == result.one_qubit_properties["1"] + == result.one_qubit_properties["0"] + == valid_oneQubitProperties + ) + assert ( + result.two_qubit_properties["0-1"] + == result.two_qubit_properties["1-2"] + == valid_twoQubitProperties + ) + assert result.supported_result_types == valid_supportedResultTypes + assert result.qubit_labels == [0, 1, 2] + assert result.fully_connected == False + assert result.directed == False + + +def test_from_json_non_fully_connected_but_directed(reduced_standardized_json_3): + result = DeviceEmulatorProperties.from_json(reduced_standardized_json_3) + assert result.qubit_count == 3 + assert result.native_gate_set == ["cz", "prx"] + assert result.connectivity_graph == {"0": ["1"], "1": ["2"]} + assert ( + result.one_qubit_properties["2"] + == result.one_qubit_properties["1"] + == result.one_qubit_properties["0"] + == valid_oneQubitProperties + ) + assert result.two_qubit_properties["0-1"] == valid_twoQubitProperties + assert result.supported_result_types == valid_supportedResultTypes + assert result.qubit_labels == [0, 1, 2] + assert result.fully_connected == False + assert result.directed == True diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py new file mode 100644 index 000000000..d8d2401cc --- /dev/null +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -0,0 +1,113 @@ +import re +from unittest.mock import Mock + +import pytest + +from braket.circuits import Circuit, Gate, Observable +from braket.circuits.noise_model import GateCriteria, NoiseModel, ObservableCriteria +from braket.circuits.noises import BitFlip +from braket.default_simulator import DensityMatrixSimulator, StateVectorSimulator +from braket.devices import local_simulator +from braket.emulation import Emulator +from braket.emulation.passes.circuit_passes import GateValidator, QubitCountValidator +from braket.devices.local_simulator import LocalSimulator + + +@pytest.fixture +def setup_local_simulator_devices(): + mock_circuit_entry = Mock() + mock_circuit_dm_entry = Mock() + mock_circuit_entry.load.return_value = StateVectorSimulator + mock_circuit_dm_entry.load.return_value = DensityMatrixSimulator + _simulator_devices = {"default": mock_circuit_entry, "braket_dm": mock_circuit_dm_entry} + local_simulator._simulator_devices.update(_simulator_devices) + + +@pytest.fixture +def local_dm_simulator(setup_local_simulator_devices): + return LocalSimulator("braket_dm", noise_model=NoiseModel()) + + +@pytest.fixture +def empty_emulator(local_dm_simulator): + return Emulator(local_dm_simulator) + + +@pytest.fixture +def noiseless_emulator(local_dm_simulator): + qubit_count_validator = QubitCountValidator(4) + return Emulator(local_dm_simulator, passes=[qubit_count_validator]) + + +@pytest.fixture +def noisy_emulator(setup_local_simulator_devices): + noise_model = NoiseModel() + noise_model.add_noise(BitFlip(0.1), GateCriteria(Gate.H)) + local_backend = LocalSimulator("braket_dm", noise_model=noise_model) + qubit_count_validator = QubitCountValidator(4) + return Emulator(local_backend, noise_model=noise_model, passes=[qubit_count_validator]) + + +def test_empty_emulator_validation(empty_emulator): + emulator = empty_emulator + circuit = Circuit().h(0).cnot(0, 1) + emulator.validate(circuit) + + +def test_noiseless_emulator(noiseless_emulator): + """ + Should not error out when passed a valid circuit. + """ + circuit = Circuit().cnot(0, 1) + circuit = noiseless_emulator.transform(circuit) + assert circuit == circuit + + +def test_basic_invalidate(noiseless_emulator): + """ + Emulator should raise an error thrown by the QubitCountValidator. + """ + circuit = Circuit().x(range(6)) + match_string = re.escape( + f"Circuit must use at most 4 qubits, but uses {circuit.qubit_count} qubits." + ) + with pytest.raises(Exception, match=match_string): + noiseless_emulator.validate(circuit) + + +def test_apply_noise_model(noisy_emulator): + circuit = Circuit().h(0) + circuit = noisy_emulator.transform(circuit) + + noisy_circuit = Circuit().h(0).apply_gate_noise(BitFlip(0.1), Gate.H).measure(target_qubits=[0]) + assert circuit == noisy_circuit + + circuit = Circuit().h(0).measure(target_qubits=[0]) + circuit = noisy_emulator.transform(circuit, apply_noise_model=False) + + target_circ = Circuit().h(0).measure(target_qubits=[0]) + assert circuit == target_circ + + +def test_remove_verbatim_box(noiseless_emulator): + circuit = Circuit().h(0) + circuit = Circuit().add_verbatim_box(circuit).probability() + circuit = noiseless_emulator._remove_verbatim_box(circuit) + + target_circuit = Circuit().h(0).probability() + + assert circuit == target_circuit + + +def test_noisy_run(noisy_emulator): + circuit = Circuit().h(0) + open_qasm_source = """OPENQASM 3.0; +bit[1] b; +qubit[1] q; +h q[0]; +#pragma braket noise bit_flip(0.1) q[0] +b[0] = measure q[0];""".strip() + + result = noisy_emulator.run(circuit, shots=1).result() + emulation_source = result.additional_metadata.action.source.strip() + assert emulation_source == open_qasm_source diff --git a/test/unit_tests/braket/emulation/test_local_emulator.py b/test/unit_tests/braket/emulation/test_local_emulator.py new file mode 100644 index 000000000..5712f65a9 --- /dev/null +++ b/test/unit_tests/braket/emulation/test_local_emulator.py @@ -0,0 +1,96 @@ +# 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. + +import pytest +import json + +from braket.emulation.device_emulator_properties import ( + DeviceEmulatorProperties, +) +from braket.device_schema.iqm.iqm_device_capabilities_v1 import IqmDeviceCapabilities +from braket.device_schema.ionq.ionq_device_capabilities_v1 import IonqDeviceCapabilities +from braket.device_schema.rigetti.rigetti_device_capabilities_v1 import RigettiDeviceCapabilities + +from braket.emulation.local_emulator import LocalEmulator + +from conftest import invalid_device_properties_dict_1, invalid_device_properties_dict_2 + + +def test_from_json_3(reduced_standardized_json): + emulator = LocalEmulator.from_json(reduced_standardized_json) + assert isinstance(emulator, LocalEmulator) + + +def test_from_device_properties(reduced_standardized_json): + device_properties = IqmDeviceCapabilities.parse_raw(reduced_standardized_json) + emulator = LocalEmulator.from_device_properties(device_properties) + assert isinstance(emulator, LocalEmulator) + + +def test_from_device_properties_non_fully_connected(reduced_standardized_json_2): + device_properties = RigettiDeviceCapabilities.parse_raw(reduced_standardized_json_2) + emulator = LocalEmulator.from_device_properties(device_properties) + assert isinstance(emulator, LocalEmulator) + + +def test_from_device_properties_non_fully_connected_but_directed(reduced_standardized_json_3): + device_properties = IqmDeviceCapabilities.parse_raw(reduced_standardized_json_3) + emulator = LocalEmulator.from_device_properties(device_properties) + assert isinstance(emulator, LocalEmulator) + + +def test_invalid_instantiation_2(reduced_standardized_json): + with pytest.raises(TypeError): + LocalEmulator.from_device_properties(reduced_standardized_json) + + +@pytest.mark.parametrize( + "invalid_device_properties_dict", + [ + (invalid_device_properties_dict_1), + (invalid_device_properties_dict_2), + ], +) +def test_noise_model_with_invalid_data(invalid_device_properties_dict): + with pytest.raises(ValueError): + LocalEmulator.from_json(json.dumps(invalid_device_properties_dict)) + + +def test_validate_valid_verbatim_circ_garnet( + reduced_standardized_json_3, valid_verbatim_circ_garnet +): + emulator = LocalEmulator.from_json(reduced_standardized_json_3) + emulator.validate(valid_verbatim_circ_garnet) + + +def test_validate_valid_verbatim_circ_ankaa3( + reduced_standardized_json_2, valid_verbatim_circ_ankaa3 +): + emulator = LocalEmulator.from_json(reduced_standardized_json_2) + emulator.validate(valid_verbatim_circ_ankaa3) + + +def test_validate_valid_verbatim_circ_aria_1( + reduced_ionq_device_capabilities_json, valid_verbatim_circ_aria1 +): + emulator = LocalEmulator.from_json(reduced_ionq_device_capabilities_json) + emulator.validate(valid_verbatim_circ_aria1) + + +def test_validate_valid_verbatim_circ_aria_1_v2( + reduced_ionq_device_capabilities_json, valid_verbatim_circ_aria1 +): + emulator = LocalEmulator.from_device_properties( + IonqDeviceCapabilities.parse_raw(reduced_ionq_device_capabilities_json) + ) + emulator.validate(valid_verbatim_circ_aria1) From da80133c287485057dd71bd61fc512555d02e576 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Aug 2025 21:53:24 +0000 Subject: [PATCH 330/347] prepare release v1.101.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51f8bffee..f3b90bb45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.101.0 (2025-08-25) + +### Features + + * Emulators with circuit validation and noise models + ## v1.100.1 (2025-08-25) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e68b69c6a..6decf1cf6 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.100.2.dev0" +__version__ = "1.101.0" From 1aac6566d484512421dc6c79e95d7f862f03c48d Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 25 Aug 2025 21:53:24 +0000 Subject: [PATCH 331/347] update development version to v1.101.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6decf1cf6..4ca589096 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.101.0" +__version__ = "1.101.1.dev0" From f8760f38e6f782d2796756d9cb63ec49ceee4fde Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Fri, 29 Aug 2025 15:29:10 -0700 Subject: [PATCH 332/347] infra: Pin pytest-rerunfailures<16.0 (#1119) --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a028d00f6..8b0affec1 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,8 @@ "pylint", "pytest", "pytest-cov", - "pytest-rerunfailures", + # https://github.com/pytest-dev/pytest-rerunfailures/issues/302 + "pytest-rerunfailures<16.0", "pytest-xdist[psutil]", "tox", ], From cadd74f70276de4923fbd5845d4886478eb575eb Mon Sep 17 00:00:00 2001 From: ses <42444435+sesmart@users.noreply.github.com> Date: Tue, 2 Sep 2025 16:10:57 -0400 Subject: [PATCH 333/347] documentation: removed "-" sign from u gate doc (#1123) --- src/braket/circuits/gates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 056134e2b..3aca9352b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -1414,7 +1414,7 @@ class U(TripleAngledGate): .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ - e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + e^{i \phi} \sin{(\theta/2)} & e^{i (\phi + \lambda)} \cos{(\theta/2)} \end{bmatrix}. Args: @@ -1489,7 +1489,7 @@ def u( .. math:: \mathtt{U}(\theta, \phi, \lambda) = \begin{bmatrix} \cos{(\theta/2)} & -e^{i \lambda} \sin{(\theta/2)} \\ - e^{i \phi} \sin{(\theta/2)} & -e^{i (\phi + \lambda)} \cos{(\theta/2)} + e^{i \phi} \sin{(\theta/2)} & e^{i (\phi + \lambda)} \cos{(\theta/2)} \end{bmatrix}. Args: From 2af105c43361fd4fa2d4cda93aa5c7dff235ce22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:14:59 +0000 Subject: [PATCH 334/347] infra: bump actions/checkout from 4.2.2 to 5.0.0 (#1121) --- .github/workflows/check-format.yml | 2 +- .github/workflows/dependent-tests.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/python-package.yml | 2 +- .github/workflows/twine-check.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 6deff2d3f..01742ef56 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -17,7 +17,7 @@ jobs: check-code-format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index 8733284c2..73d10fb70 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -20,7 +20,7 @@ jobs: - amazon-braket-pennylane-plugin-python steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e2b542389..8dc920e02 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,7 +12,7 @@ jobs: name: Build and publish distribution to PyPi runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 79b619974..b52e3e8c0 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -25,7 +25,7 @@ jobs: python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: diff --git a/.github/workflows/twine-check.yml b/.github/workflows/twine-check.yml index 628155830..16b8d0df7 100644 --- a/.github/workflows/twine-check.yml +++ b/.github/workflows/twine-check.yml @@ -15,7 +15,7 @@ jobs: name: Check long description runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: From 6dfcaac5c90d9f6335a5085907c6a270b7504761 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 20:25:29 +0000 Subject: [PATCH 335/347] infra: bump codecov/codecov-action from 5.4.3 to 5.5.0 (#1122) --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b52e3e8c0..badaa7bc3 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,7 +37,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5.4.3 + uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 6ca468b13edaabea4025c53cc634ce6b89f4c3ff Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 3 Sep 2025 16:17:27 +0000 Subject: [PATCH 336/347] prepare release v1.101.0.post0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b90bb45..711166284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.101.0.post0 (2025-09-03) + +### Documentation Changes + + * removed "-" sign from u gate doc + ## v1.101.0 (2025-08-25) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4ca589096..e6c1757d7 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.101.1.dev0" +__version__ = "1.101.0.post0" From b98a75e78b91b69c3a04a34b878651e286c112a4 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 3 Sep 2025 16:17:27 +0000 Subject: [PATCH 337/347] update development version to v1.101.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e6c1757d7..4ca589096 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.101.0.post0" +__version__ = "1.101.1.dev0" From 514b695e62fa8b8209dfea5a5aee4d332c7ff874 Mon Sep 17 00:00:00 2001 From: nilsquet <226726448+nilsquet@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:14:18 -0400 Subject: [PATCH 338/347] feature: add barrier instruction (#1118) --- src/braket/circuits/circuit.py | 23 ++ src/braket/circuits/compiler_directives.py | 35 +++ src/braket/circuits/moments.py | 26 ++- .../ascii_circuit_diagram.py | 208 ++++++++++++------ .../text_circuit_diagram_utils.py | 62 ++++-- .../unicode_circuit_diagram.py | 30 ++- .../circuits/test_ascii_circuit_diagram.py | 101 +++++++++ .../braket/circuits/test_circuit.py | 47 ++++ .../circuits/test_compiler_directives.py | 19 ++ .../braket/circuits/test_moments.py | 15 ++ .../circuits/test_unicode_circuit_diagram.py | 91 ++++++++ 11 files changed, 556 insertions(+), 101 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 9268abad7..7d024342e 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -729,6 +729,29 @@ def add_verbatim_box( self._has_compiler_directives = True return self + def barrier(self, target: QubitSetInput | None = None) -> Circuit: + """Add a barrier compiler directive to the circuit. + + Args: + target (QubitSetInput | None): Target qubits for the barrier. + If None, applies to all qubits in the circuit. + + Returns: + Circuit: self + + Examples: + >>> circ = Circuit().h(0).barrier([0, 1]).cnot(0, 1) + >>> circ = Circuit().h(0).h(1).barrier() # barrier on all qubits + """ + target_qubits = self.qubits if target is None else QubitSet(target) + + if target_qubits: + self.add_instruction( + Instruction(compiler_directives.Barrier(list(target_qubits)), target=target_qubits) + ) + self._has_compiler_directives = True + return self + def _add_measure(self, target_qubits: QubitSetInput) -> None: """Adds a measure instruction to the the circuit diff --git a/src/braket/circuits/compiler_directives.py b/src/braket/circuits/compiler_directives.py index 0cd325776..329dcc3f6 100644 --- a/src/braket/circuits/compiler_directives.py +++ b/src/braket/circuits/compiler_directives.py @@ -16,6 +16,8 @@ import braket.ir.jaqcd as ir from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.serialization import IRType, SerializationProperties +from braket.registers.qubit_set import QubitSet class StartVerbatimBox(CompilerDirective): @@ -60,3 +62,36 @@ def _to_jaqcd(self, *args, **kwargs) -> Any: def _to_openqasm(self) -> str: return "}" + + +class Barrier(CompilerDirective): + """Barrier compiler directive.""" + + def __init__(self, qubit_indices: list[int]): + super().__init__(["||"]) + self._qubit_indices = qubit_indices + + @property + def qubit_indices(self) -> list[int]: + return self._qubit_indices + + @property + def qubit_count(self) -> int: + return len(self._qubit_indices) + + def _to_jaqcd(self) -> Any: + raise NotImplementedError("Barrier is not supported in JAQCD") + + def to_ir( + self, + target: QubitSet | None, + ir_type: IRType, + serialization_properties: SerializationProperties | None = None, + **kwargs, + ) -> Any: + if ir_type.name == "OPENQASM": + if target: + qubits = ", ".join(serialization_properties.format_target(int(q)) for q in target) + return f"barrier {qubits};" + return "barrier;" + return super().to_ir(target, ir_type, serialization_properties, **kwargs) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 10f236366..9121125b7 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -174,10 +174,17 @@ def add(self, instructions: Iterable[Instruction] | Instruction, noise_index: in def _add(self, instruction: Instruction, noise_index: int = 0) -> None: operator = instruction.operator if isinstance(operator, CompilerDirective): - time = self._update_qubit_times(self._qubits) - self._moments[MomentsKey(time, None, MomentType.COMPILER_DIRECTIVE, 0)] = instruction + qubit_range = instruction.target.union(instruction.control or QubitSet()) + time = self._handle_compiler_directive(operator, qubit_range) + # For barriers without qubits, use empty qubit set for the key + key_qubits = ( + QubitSet() if operator.name == "Barrier" and not qubit_range else qubit_range + ) + self._moments[MomentsKey(time, key_qubits, MomentType.COMPILER_DIRECTIVE, 0)] = ( + instruction + ) + self._qubits.update(qubit_range) self._depth = time + 1 - self._time_all_qubits = time elif isinstance(operator, Noise): self.add_noise(instruction) elif isinstance(operator, Gate) and operator.name == "GPhase": @@ -282,6 +289,17 @@ def sort_moments(self) -> None: self._moments = sorted_moment + def _handle_compiler_directive(self, operator: CompilerDirective, qubit_range: QubitSet) -> int: + """Handle compiler directive and return the time slot.""" + if operator.name == "Barrier" and not qubit_range: + time = self._get_qubit_times(self._qubits) + 1 + self._time_all_qubits = time + else: + time = self._update_qubit_times(qubit_range or self._qubits) + if operator.name != "Barrier": + self._time_all_qubits = time + return time + def _max_time_for_qubit(self, qubit: Qubit) -> int: # -1 if qubit is unoccupied because the first instruction will have an index of 0 return self._max_times.get(qubit, -1) @@ -307,7 +325,7 @@ def values(self) -> ValuesView[Instruction]: self.sort_moments() return self._moments.values() - def get(self, key: MomentsKey, default: Any | None = None) -> Instruction: + def get(self, key: MomentsKey, default: Any | None = None) -> Instruction | Any | None: """Get the instruction in self by key. Args: diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index 04bd7f059..91b4c1abf 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -70,6 +70,125 @@ def _duplicate_time_at_bottom(cls, lines: str) -> None: # duplicate times after an empty line lines.append(lines[0]) + @classmethod + def _process_item_properties( + cls, item: Instruction | ResultType, circuit_qubits: QubitSet + ) -> tuple[QubitSet, QubitSet, QubitSet, QubitSet, list[str], dict | None]: + """Extract properties from an item, keeping original logic structure.""" + if isinstance(item, ResultType) and not item.target: + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + map_control_qubit_states = None + elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): + if item.operator.name == "Barrier": + target_qubits = item.target + if not target_qubits: + # Barrier without qubits - single barrier across all qubits + target_qubits = circuit_qubits + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + else: + # Barrier with specific qubits + qubits = target_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(target_qubits) + target_and_control = target_qubits + else: + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbol = item.ascii_symbols[0] + marker = "*" * len(ascii_symbol) + num_after = len(circuit_qubits) - 1 + after = ["|"] * (num_after - 1) + ([marker] if num_after else []) + ascii_symbols = [ascii_symbol, *after] + control_qubits = QubitSet() + map_control_qubit_states = None + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = QubitSet() + qubits = circuit_qubits + ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) + map_control_qubit_states = None + else: + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target + control_qubits = getattr(item, "control", QubitSet()) + control_state = getattr(item, "control_state", "1" * len(control_qubits)) + map_control_qubit_states = dict(zip(control_qubits, control_state, strict=True)) + target_and_control = target_qubits.union(control_qubits) + qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + ascii_symbols = item.ascii_symbols + + return ( + target_qubits, + control_qubits, + target_and_control, + qubits, + ascii_symbols, + map_control_qubit_states, + ) + + @classmethod + def _update_qubit_symbols_and_connections( + cls, + item: Instruction | ResultType, + qubit: int, + target_qubits: QubitSet, + control_qubits: QubitSet, + target_and_control: QubitSet, + ascii_symbols: list[str], + symbols: dict, + connections: dict, + map_control_qubit_states: dict | None, + ) -> None: + """Update symbols and connections for a qubit, keeping original logic.""" + # Determine if the qubit is part of the item or in the middle of a + # multi qubit item. + if qubit in target_qubits: + item_qubit_index = next(index for index, q in enumerate(target_qubits) if q == qubit) + power_string = ( + f"^{power}" + if ( + (power := getattr(item, "power", 1)) != 1 + # this has the limitation of not printing the power + # when a user has a gate genuinely named C, but + # is necessary to enable proper printing of custom + # gates with built-in control qubits + and ascii_symbols[item_qubit_index] != "C" + ) + else "" + ) + idx = item_qubit_index + symbols[qubit] = ( + f"({ascii_symbols[idx]}{power_string})" if power_string else ascii_symbols[idx] + ) + elif qubit in control_qubits: + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" + else: + symbols[qubit] = "|" + + # Set the margin to be a connector if not on the first qubit + if target_and_control and qubit != min(target_and_control): + is_barrier = ( + isinstance(item, Instruction) + and isinstance(item.operator, CompilerDirective) + and item.operator.name == "Barrier" + ) + # Add vertical lines for non-barriers or global barriers (no target) + if not is_barrier or not item.target: + connections[qubit] = "above" + @classmethod def _create_diagram_column( cls, @@ -91,78 +210,27 @@ def _create_diagram_column( connections = dict.fromkeys(circuit_qubits, "none") for item in items: - if isinstance(item, ResultType) and not item.target: - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) - elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbol = item.ascii_symbols[0] - marker = "*" * len(ascii_symbol) - num_after = len(circuit_qubits) - 1 - after = ["|"] * (num_after - 1) + ([marker] if num_after else []) - ascii_symbols = [ascii_symbol, *after] - elif ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = QubitSet() - qubits = circuit_qubits - ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) - else: - if isinstance(item.target, list): - target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target_qubits = item.target - control_qubits = getattr(item, "control", QubitSet()) - control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = dict(zip(control_qubits, control_state, strict=True)) - - target_and_control = target_qubits.union(control_qubits) - qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) - - ascii_symbols = item.ascii_symbols + ( + target_qubits, + control_qubits, + target_and_control, + qubits, + ascii_symbols, + map_control_qubit_states, + ) = cls._process_item_properties(item, circuit_qubits) for qubit in qubits: - # Determine if the qubit is part of the item or in the middle of a - # multi qubit item. - if qubit in target_qubits: - item_qubit_index = [ # noqa: RUF015 - index for index, q in enumerate(target_qubits) if q == qubit - ][0] - power_string = ( - f"^{power}" - if ( - (power := getattr(item, "power", 1)) != 1 - # this has the limitation of not printing the power - # when a user has a gate genuinely named C, but - # is necessary to enable proper printing of custom - # gates with built-in control qubits - and ascii_symbols[item_qubit_index] != "C" - ) - else "" - ) - symbols[qubit] = ( - f"({ascii_symbols[item_qubit_index]}{power_string})" - if power_string - else ascii_symbols[item_qubit_index] - ) - elif qubit in control_qubits: - symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" - else: - symbols[qubit] = "|" - - # Set the margin to be a connector if not on the first qubit - if target_and_control and qubit != min(target_and_control): - connections[qubit] = "above" + cls._update_qubit_symbols_and_connections( + item, + qubit, + target_qubits, + control_qubits, + target_and_control, + ascii_symbols, + symbols, + connections, + map_control_qubit_states, + ) return cls._create_output(symbols, connections, circuit_qubits, global_phase) diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 1e3dcae6b..2d1872cb9 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -112,6 +112,44 @@ def _compute_moment_global_phase( return global_phase + moment_phase if global_phase is not None else None +def _get_qubit_range_for_item(item: Instruction | ResultType, circuit_qubits: QubitSet) -> QubitSet: + """Get the qubit range for a given item.""" + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + return QubitSet() + + if isinstance(item, ResultType) and not item.target: + return circuit_qubits + + if isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): + return _get_compiler_directive_qubit_range(item, circuit_qubits) + + return _get_standard_qubit_range(item) + + +def _get_compiler_directive_qubit_range(item: Instruction, circuit_qubits: QubitSet) -> QubitSet: + """Get qubit range for compiler directive instructions.""" + if item.operator.name == "Barrier": + if not item.target or len(item.target) == 0: + return circuit_qubits + return item.target + return circuit_qubits + + +def _get_standard_qubit_range(item: Instruction | ResultType) -> QubitSet: + """Get qubit range for standard instructions and result types.""" + if isinstance(item.target, list): + target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target = item.target + control = getattr(item, "control", QubitSet()) + target_and_control = target.union(control) + return QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + + def _group_items( circuit_qubits: QubitSet, items: list[Instruction | ResultType], @@ -128,33 +166,13 @@ def _group_items( """ groupings = [] for item in items: - # Can only print QuantumOperator and CompilerDirective operators for instructions at - # the moment + # Can only print QuantumOperator and CompilerDirective operators for instructions if isinstance(item, Instruction) and not isinstance( item.operator, CompilerDirective | QuantumOperator ): continue - # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range - # to an empty list and we just add it to the first group below. - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - qubit_range = QubitSet() - elif (isinstance(item, ResultType) and not item.target) or ( - isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) - ): - qubit_range = circuit_qubits - else: - if isinstance(item.target, list): - target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target = item.target - control = getattr(item, "control", QubitSet()) - target_and_control = target.union(control) - qubit_range = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + qubit_range = _get_qubit_range_for_item(item, circuit_qubits) found_grouping = False for group in groupings: diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 4b49bf90a..e7d00f675 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -140,14 +140,34 @@ def _build_parameters( ) -> tuple: map_control_qubit_states = {} - if (isinstance(item, ResultType) and not item.target) or ( - isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) - ): + if isinstance(item, ResultType) and not item.target: target_qubits = circuit_qubits control_qubits = QubitSet() qubits = circuit_qubits ascii_symbols = [item.ascii_symbols[0]] * len(qubits) cls._update_connections(qubits, connections) + elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): + if item.operator.name == "Barrier": + if not item.target: + # Barrier without qubits - single barrier across all qubits WITH connections + target_qubits = circuit_qubits + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + cls._update_connections(circuit_qubits, connections) + else: + # Barrier with specific qubits - only add connections for global barriers + target_qubits = item.target + qubits = target_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(target_qubits) + # Specific barriers get no vertical lines + # (Global barriers are handled above with no target) + control_qubits = QubitSet() + else: + target_qubits = circuit_qubits + control_qubits = QubitSet() + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(qubits) + cls._update_connections(qubits, connections) elif ( isinstance(item, Instruction) and isinstance(item.operator, Gate) @@ -209,12 +229,12 @@ def _draw_symbol( """ top = "" bottom = "" - if symbol in {"C", "N", "SWAP"}: + if symbol in {"C", "N", "SWAP", "||"}: if connection in {"above", "both"}: top = _fill_symbol(cls._vertical_delimiter(), " ") if connection in {"below", "both"}: bottom = _fill_symbol(cls._vertical_delimiter(), " ") - new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} + new_symbol = {"C": "●", "N": "◯", "SWAP": "x", "||": "▒"} # replace SWAP by x # the size of the moment remains as if there was a box with 4 characters inside symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 901bbbb9b..e86f42425 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -14,6 +14,7 @@ import numpy as np import pytest +from braket.circuits.compiler_directives import Barrier from braket.circuits import ( AsciiCircuitDiagram, Circuit, @@ -312,6 +313,42 @@ def test_overlapping_qubits(): _assert_correct_diagram(circ, expected) +def test_barrier_single_qubit(): + circ = Circuit().x(0).x(1).barrier(target=[0]).h(2) + expected = ( + "T : |0|1 |", + " ", + "q0 : -X-||-", + " ", + "q1 : -X----", + " ", + "q2 : -H----", + "", + "T : |0|1 |", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_global_with_vertical_lines(): + from braket.circuits.compiler_directives import Barrier + + circ = Circuit().x(0).x(1) + circ.add_instruction(Instruction(Barrier([]), [])) + circ.h(2) + expected = ( + "T : |0|1 |2|", + " ", + "q0 : -X-||---", + " | ", + "q1 : -X-||---", + " | ", + "q2 : ---||-H-", + "", + "T : |0|1 |2|", + ) + _assert_correct_diagram(circ, expected) + + def test_overlapping_qubits_angled_gates(): circ = Circuit().zz(0, 2, 0.15).x(control=1, target=3).h(0) expected = ( @@ -956,3 +993,67 @@ def test_measure_with_readout_noise(): "T : |0| 1 |2|", ) _assert_correct_diagram(circ, expected) + + +def test_barrier_circuit_visualization_without_other_gates(): + circ = Circuit().barrier(target=[0, 100]) + expected = ( + "T : |0 |", + " ", + "q0 : -||-", + " ", + "q100 : -||-", + "", + "T : |0 |", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_circuit_visualization_with_other_gates(): + circ = Circuit().x(0).barrier(target=[0, 100]).h(3) + expected = ( + "T : |0|1 |", + " ", + "q0 : -X-||-", + " ", + "q3 : -H----", + " ", + "q100 : ---||-", + "", + "T : |0|1 |", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_multiple_qubits_with_gates(): + circ = Circuit().x(0).x(1).barrier(target=[0, 1]).h(0).h(2) + expected = ( + "T : |0|1 |2|", + " ", + "q0 : -X-||-H-", + " ", + "q1 : -X-||---", + " ", + "q2 : -H------", + "", + "T : |0|1 |2|", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_global_with_vertical_lines(): + circ = Circuit().x(0).x(1) + circ.add_instruction(Instruction(Barrier([]), [])) + circ.h(2) + expected = ( + "T : |0|1 |2|", + " ", + "q0 : -X-||---", + " | ", + "q1 : -X-||---", + " | ", + "q2 : ---||-H-", + "", + "T : |0|1 |2|", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index a76794bec..fd18f2a6e 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -3589,3 +3589,50 @@ def test_from_ir_round_trip_transformation_with_targeted_measurements(): assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") + + +def test_barrier_specific_qubits(): + circ = Circuit().barrier([0, 1, 2]) + assert len(circ.instructions) == 1 + instr = circ.instructions[0] + assert isinstance(instr.operator, compiler_directives.Barrier) + assert instr.target == QubitSet([0, 1, 2]) + assert instr.operator.qubit_indices == [0, 1, 2] + assert circ.qubits_frozen is True + + +def test_barrier_all_qubits(): + circ = Circuit().h(0).h(1).barrier() + assert len(circ.instructions) == 3 + barrier_instr = circ.instructions[2] + assert isinstance(barrier_instr.operator, compiler_directives.Barrier) + assert barrier_instr.target == QubitSet([0, 1]) + + +def test_barrier_empty_circuit(): + circ = Circuit().barrier() + assert len(circ.instructions) == 0 # No barrier added to empty circuit + + +def test_barrier_none_target(): + circ = Circuit().h(0).h(2).barrier(None) + barrier_instr = circ.instructions[2] + assert barrier_instr.target == QubitSet([0, 2]) + + +def test_barrier_openqasm_export_specific_qubits(): + circ = Circuit().h(0).barrier([0, 1]).cnot(0, 1) + qasm = circ.to_ir(IRType.OPENQASM).source + assert "barrier q[0], q[1];" in qasm + + +def test_barrier_openqasm_export_all_qubits(): + circ = Circuit().h(0).h(1).barrier().cnot(0, 1) + qasm = circ.to_ir(IRType.OPENQASM).source + assert "barrier q[0], q[1];" in qasm + + +def test_barrier_jaqcd_export_fails(): + circ = Circuit().h(0).barrier([0, 1]) + with pytest.raises(NotImplementedError, match="Barrier is not supported in JAQCD"): + circ.to_ir(IRType.JAQCD) diff --git a/test/unit_tests/braket/circuits/test_compiler_directives.py b/test/unit_tests/braket/circuits/test_compiler_directives.py index a05e4c20a..ae1e4905f 100644 --- a/test/unit_tests/braket/circuits/test_compiler_directives.py +++ b/test/unit_tests/braket/circuits/test_compiler_directives.py @@ -17,6 +17,7 @@ from braket.circuits import compiler_directives from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.serialization import IRType +from braket.circuits.serialization import OpenQASMSerializationProperties, QubitReferenceType @pytest.mark.parametrize( @@ -49,3 +50,21 @@ def test_verbatim(testclass, irclass, openqasm_str, counterpart): assert directive is not op assert directive != CompilerDirective(ascii_symbols=["foo"]) assert directive != "not a directive" + + +def test_barrier(): + barrier = compiler_directives.Barrier([0, 1, 2]) + assert barrier.qubit_indices == [0, 1, 2] + assert barrier.qubit_count == 3 + assert barrier.ascii_symbols == ("||",) + assert repr(barrier) == "Barrier" + + with pytest.raises(NotImplementedError, match="Barrier is not supported in JAQCD"): + barrier._to_jaqcd() + + props = OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL) + result = barrier.to_ir([0, 1, 2], IRType.OPENQASM, props) + assert result == "barrier q[0], q[1], q[2];" + + result = barrier.to_ir([], IRType.OPENQASM, props) + assert result == "barrier;" diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index ed45b0aa3..f2930e6ce 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -16,6 +16,7 @@ import pytest from braket.circuits import Gate, Instruction, Moments, MomentsKey, QubitSet +from braket.circuits.compiler_directives import Barrier def cnot(q1, q2): @@ -185,3 +186,17 @@ def test_repr(moments): def test_str(moments): assert str(moments) == str(OrderedDict(moments)) + + +def test_barrier_with_qubits(): + """Test barrier with specific qubits.""" + moments = Moments([h(0), h(1)]) + moments.add(Instruction(Barrier([0]), [0])) + assert moments.depth == 2 + + +def test_barrier_without_qubits(): + """Test barrier without qubits (global).""" + moments = Moments([h(0), h(1)]) + moments.add(Instruction(Barrier([]), [])) + assert moments.depth == 2 diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index a0288ddda..0cc31ecc9 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -26,6 +26,8 @@ ) from braket.pulse import Frame, Port, PulseSequence +from braket.circuits.compiler_directives import Barrier + def _assert_correct_diagram(circ, expected): assert UnicodeCircuitDiagram.build_diagram(circ) == "\n".join(expected) @@ -1119,3 +1121,92 @@ def test_measure_with_readout_noise(): "T : │ 0 │ 1 │ 2 │", ) _assert_correct_diagram(circ, expected) + + +def test_barrier_circuit_visualization_without_other_gates(): + circ = Circuit().barrier(target=[0, 100]) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───▒────", + " ", + " ", + "q100 : ───▒────", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_circuit_visualization_with_other_gates(): + circ = Circuit().x(0).barrier(target=[0, 100]).h(3) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ─┤ X ├───▒────", + " └───┘ ", + " ┌───┐ ", + "q3 : ─┤ H ├────────", + " └───┘ ", + " ", + "q100 : ─────────▒────", + " ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_single_qubit(): + circ = Circuit().x(0).x(1).barrier(target=[0]).h(2) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ─┤ X ├───▒────", + " └───┘ ", + " ┌───┐ ", + "q1 : ─┤ X ├────────", + " └───┘ ", + " ┌───┐ ", + "q2 : ─┤ H ├────────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_multiple_qubits_with_gates(): + circ = Circuit().x(0).x(1).barrier(target=[0, 1]).h(0).h(2) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ X ├───▒────┤ H ├─", + " └───┘ └───┘ ", + " ┌───┐ ", + "q1 : ─┤ X ├───▒──────────", + " └───┘ ", + " ┌───┐ ", + "q2 : ─┤ H ├──────────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_barrier_global_with_vertical_lines(): + circ = Circuit().x(0).x(1) + circ.add_instruction(Instruction(Barrier([]), [])) + circ.h(2) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ", + "q0 : ─┤ X ├───▒──────────", + " └───┘ │ ", + " ┌───┐ │ ", + "q1 : ─┤ X ├───▒──────────", + " └───┘ │ ", + " │ ┌───┐ ", + "q2 : ─────────▒────┤ H ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) From 2f86fccf03a90125af123f77ee59a8139482e45c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Sep 2025 16:17:15 +0000 Subject: [PATCH 339/347] prepare release v1.102.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 711166284..d804fd613 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.102.0 (2025-09-08) + +### Features + + * add barrier instruction + ## v1.101.0.post0 (2025-09-03) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 4ca589096..a9e2e75ac 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.101.1.dev0" +__version__ = "1.102.0" From ae4b916050de1ab6b8afa9f53636be65e737aaf4 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Sep 2025 16:17:15 +0000 Subject: [PATCH 340/347] update development version to v1.102.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a9e2e75ac..f45edcbe8 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.102.0" +__version__ = "1.102.1.dev0" From ef5f17203cf06f531cc199ce0d8d34c7240ad6ab Mon Sep 17 00:00:00 2001 From: maolinml <86260930+maolinml@users.noreply.github.com> Date: Wed, 10 Sep 2025 08:40:52 -0700 Subject: [PATCH 341/347] fix: emulator validation for program sets (#1125) Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/emulation/emulator.py | 3 ++- .../braket/emulation/test_local_emulator.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/braket/emulation/emulator.py b/src/braket/emulation/emulator.py index df8275aba..31945ca4b 100644 --- a/src/braket/emulation/emulator.py +++ b/src/braket/emulation/emulator.py @@ -129,6 +129,8 @@ def transform( exists for this emulator and apply_noise_model is true. """ + program = self._pass_manager.transform(task_specification) + # Apply measurement manually if the circuit has no measurement and no result type. # This ensures that the noise model can apply readout error to the circuit, since # the readout error is applied if and only if there is measurement or result type @@ -140,7 +142,6 @@ def transform( if (not has_measurement) and len(task_specification.result_types) == 0: task_specification.measure(target_qubits=task_specification.qubits) - program = self._pass_manager.transform(task_specification) return ( self._noise_model.apply(program) if apply_noise_model and self.noise_model else program ) diff --git a/test/unit_tests/braket/emulation/test_local_emulator.py b/test/unit_tests/braket/emulation/test_local_emulator.py index 5712f65a9..eabaac39b 100644 --- a/test/unit_tests/braket/emulation/test_local_emulator.py +++ b/test/unit_tests/braket/emulation/test_local_emulator.py @@ -14,15 +14,15 @@ import pytest import json -from braket.emulation.device_emulator_properties import ( - DeviceEmulatorProperties, -) from braket.device_schema.iqm.iqm_device_capabilities_v1 import IqmDeviceCapabilities from braket.device_schema.ionq.ionq_device_capabilities_v1 import IonqDeviceCapabilities from braket.device_schema.rigetti.rigetti_device_capabilities_v1 import RigettiDeviceCapabilities from braket.emulation.local_emulator import LocalEmulator +from braket.program_sets import ProgramSet +from braket.circuits import Circuit + from conftest import invalid_device_properties_dict_1, invalid_device_properties_dict_2 @@ -94,3 +94,10 @@ def test_validate_valid_verbatim_circ_aria_1_v2( IonqDeviceCapabilities.parse_raw(reduced_ionq_device_capabilities_json) ) emulator.validate(valid_verbatim_circ_aria1) + + +def test_program_set(reduced_standardized_json): + emulator = LocalEmulator.from_json(reduced_standardized_json) + ps = ProgramSet([Circuit()], shots_per_executable=50) + with pytest.raises(TypeError): + emulator.run(ps) From 22a72a4bfe8213cc3d25c4dc0fabc88b2c2c0130 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Sep 2025 16:16:50 +0000 Subject: [PATCH 342/347] prepare release v1.102.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d804fd613..ca02ca4bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.102.1 (2025-09-10) + +### Bug Fixes and Other Changes + + * emulator validation for program sets + ## v1.102.0 (2025-09-08) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f45edcbe8..e4e0f761d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.102.1.dev0" +__version__ = "1.102.1" From 8d7cb7883f057c38f331a37a21b7378342a95e33 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Sep 2025 16:16:50 +0000 Subject: [PATCH 343/347] update development version to v1.102.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e4e0f761d..66b26c957 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.102.1" +__version__ = "1.102.2.dev0" From a0a2a6fbe63ece3ef643e190719d5d96b9a107ce Mon Sep 17 00:00:00 2001 From: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:23:48 -0700 Subject: [PATCH 344/347] infra: retrieve github envs correctly for workflow file (#1126) --- .github/workflows/code-freeze.yml | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/.github/workflows/code-freeze.yml b/.github/workflows/code-freeze.yml index d19f5b48d..e3e528cfa 100644 --- a/.github/workflows/code-freeze.yml +++ b/.github/workflows/code-freeze.yml @@ -20,22 +20,13 @@ jobs: - name: Fetch PR data and check if merge allowed if: env.FROZEN == 'true' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_TITLE: ${{ github.event.pull_request.title }} + BRANCH_NAME: ${{ github.event.pull_request.head.ref }} run: | - PR_DATA=$(curl -s \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}) - BRANCH_NAME=$(echo $PR_DATA | jq .head.ref -r) - PR_TITLE=$(echo $PR_DATA | jq .title -r) - - echo $BRANCH_NAME - echo $PR_TITLE - # if it's not a critical fix - if ! [[ "$PR_TITLE" == fix\(critical\):* ]]; then + if ! [[ "$PR_TITLE" == fix(critical):* ]]; then # and there's an unfrozen prefix - if ! [[ -z $UNFROZEN_PREFIX ]]; then + if [[ -n "${UNFROZEN_PREFIX:-}" ]]; then # check if the branch matches unfrozen prefix if [[ "$BRANCH_NAME" != $UNFROZEN_PREFIX* ]]; then echo "Error: You can only merge from branches that start with '$UNFROZEN_PREFIX', or PRs titled with prefix 'fix(critical): '." @@ -46,4 +37,4 @@ jobs: echo "Error: You can only merge PRs titled with prefix 'fix(critical): '." exit 1 fi - fi + fi \ No newline at end of file From 8a9f384f67dd291175d7a54222cf9a84b32c1096 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Wed, 10 Sep 2025 13:45:23 -0700 Subject: [PATCH 345/347] fix: Python 3.10 syntax (#1120) --- pyproject.toml | 2 + setup.cfg | 2 - .../ahs/analog_hamiltonian_simulation.py | 82 +++++++------- src/braket/aws/aws_device.py | 2 +- src/braket/aws/aws_quantum_task.py | 44 +++----- src/braket/aws/aws_session.py | 2 +- src/braket/circuits/angled_gate.py | 9 +- src/braket/circuits/basis_state.py | 70 ++++++------ src/braket/circuits/circuit.py | 2 +- src/braket/circuits/moments.py | 101 +++++++++-------- .../circuits/noise_model/noise_model.py | 17 +-- .../text_circuit_diagram.py | 13 +++ src/braket/circuits/translations.py | 105 +++++------------- src/braket/jobs/local/local_job_container.py | 1 + src/braket/jobs/metrics.py | 1 + src/braket/jobs/metrics_data/definitions.py | 1 + src/braket/jobs/quantum_job.py | 1 + src/braket/jobs/quantum_job_creation.py | 1 + src/braket/program_sets/parameter_sets.py | 17 +-- src/braket/pulse/ast/approximation_parser.py | 4 +- .../tasks/gate_model_quantum_task_result.py | 87 +++++++-------- src/braket/tasks/local_quantum_task.py | 1 + src/braket/tracking/tracker.py | 66 +++++------ .../gate_model_device_testing_utils.py | 4 +- .../test_density_matrix_simulator.py | 13 +++ test/integ_tests/test_device_creation.py | 1 - test/integ_tests/test_pulse.py | 13 +++ test/integ_tests/test_queue_information.py | 1 - test/integ_tests/test_user_agents.py | 15 ++- .../circuits/noise/test_measure_criteria.py | 13 +++ .../braket/circuits/test_basis_state.py | 13 +++ .../braket/circuits/test_result_type.py | 1 + .../braket/devices/test_local_simulator.py | 12 +- .../passes/test_connectivity_validator.py | 13 +++ .../test_gate_connectivity_validator.py | 13 +++ .../emulation/passes/test_gate_validator.py | 13 +++ .../passes/test_qubit_count_validator.py | 13 +++ .../passes/test_result_type_validator.py | 13 +++ .../braket/emulation/test_emulator.py | 13 +++ .../braket/jobs/test_environment_variables.py | 13 +++ .../unit_tests/braket/jobs/test_hybrid_job.py | 13 +++ test/unit_tests/braket/jobs/test_metrics.py | 13 +++ .../braket/jobs/test_serialization.py | 1 - .../pulse/ast/test_approximation_parser.py | 3 +- test/unit_tests/braket/test_imports.py | 13 +++ .../braket/tracking/test_pricing.py | 1 - 46 files changed, 496 insertions(+), 356 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3d937ada7..b83e45e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,8 @@ lint.ignore = [ "N806", # Vars will break this rule based on technical names. "N815", # Allowing reviwers to do a per case basis "PLC2701", # Allow private function access in code + "PLR0904", # Too many public methods + "PLR0911", # Too many return statements "PLR0913", # Too many variables in function "PLR0914", # Too many local variables "PLR0917", # Too many positional arguments diff --git a/setup.cfg b/setup.cfg index 72e69bcd7..b7e478982 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,2 @@ [aliases] test=pytest - - diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index 4f6dda34e..19947de34 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -14,7 +14,6 @@ from __future__ import annotations from collections import defaultdict -from functools import singledispatch from typing import TYPE_CHECKING import braket.ir.ahs as ir @@ -162,48 +161,43 @@ def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: ) -@singledispatch def _get_term_ir( term: Hamiltonian, -) -> tuple[str, dict]: - raise TypeError(f"Unable to convert Hamiltonian term type {type(term)}.") - - -@_get_term_ir.register -def _(term: LocalDetuning) -> tuple[str, ir.LocalDetuning]: - return AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY, ir.LocalDetuning( - magnitude=ir.PhysicalField( - time_series=ir.TimeSeries( - times=term.magnitude.time_series.times(), - values=term.magnitude.time_series.values(), - ), - pattern=term.magnitude.pattern.series, - ) - ) - - -@_get_term_ir.register -def _(term: DrivingField) -> tuple[str, ir.DrivingField]: - return AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY, ir.DrivingField( - amplitude=ir.PhysicalField( - time_series=ir.TimeSeries( - times=term.amplitude.time_series.times(), - values=term.amplitude.time_series.values(), - ), - pattern="uniform", - ), - phase=ir.PhysicalField( - time_series=ir.TimeSeries( - times=term.phase.time_series.times(), - values=term.phase.time_series.values(), - ), - pattern="uniform", - ), - detuning=ir.PhysicalField( - time_series=ir.TimeSeries( - times=term.detuning.time_series.times(), - values=term.detuning.time_series.values(), - ), - pattern="uniform", - ), - ) +) -> tuple[str, ir.LocalDetuning | ir.DrivingField]: + match term: + case LocalDetuning(magnitude=magnitude): + return AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY, ir.LocalDetuning( + magnitude=ir.PhysicalField( + time_series=ir.TimeSeries( + times=magnitude.time_series.times(), + values=magnitude.time_series.values(), + ), + pattern=magnitude.pattern.series, + ) + ) + case DrivingField(amplitude=amplitude, phase=phase, detuning=detuning): + return AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY, ir.DrivingField( + amplitude=ir.PhysicalField( + time_series=ir.TimeSeries( + times=amplitude.time_series.times(), + values=amplitude.time_series.values(), + ), + pattern="uniform", + ), + phase=ir.PhysicalField( + time_series=ir.TimeSeries( + times=phase.time_series.times(), + values=phase.time_series.values(), + ), + pattern="uniform", + ), + detuning=ir.PhysicalField( + time_series=ir.TimeSeries( + times=detuning.time_series.times(), + values=detuning.time_series.values(), + ), + pattern="uniform", + ), + ) + case _: + raise TypeError(f"Unable to convert Hamiltonian term type {type(term)}.") diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 3ca7f181a..0c5e8c72d 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -58,7 +58,7 @@ class AwsDeviceType(str, Enum): QPU = "QPU" -class AwsDevice(Device): # noqa: PLR0904 +class AwsDevice(Device): """Amazon Braket implementation of a device. Use this class to retrieve the latest metadata about the device and to run a quantum task on the device. diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index bdf605061..d0972a069 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -891,37 +891,21 @@ def _create_program_set_task(aws_session: AwsSession, **create_task_kwargs: dict raise ClientError(response, e.operation_name) from e -@singledispatch def _format_result( result: GateModelTaskResult | ProgramSetTaskResult | AnalogHamiltonianSimulationTaskResult, task_specification: TaskSpecification, ) -> TaskResult: - raise TypeError("Invalid result specification type") - - -@_format_result.register -def _(result: GateModelTaskResult, _: TaskSpecification) -> GateModelQuantumTaskResult: - GateModelQuantumTaskResult.cast_result_types(result) - return GateModelQuantumTaskResult.from_object(result) - - -@_format_result.register -def _(result: ProgramSetTaskResult, program_set: ProgramSet) -> ProgramSetQuantumTaskResult: - return ProgramSetQuantumTaskResult.from_object(result, program_set=program_set) - - -@_format_result.register -def _(result: AnnealingTaskResult, _: TaskSpecification) -> AnnealingQuantumTaskResult: - return AnnealingQuantumTaskResult.from_object(result) - - -@_format_result.register -def _(result: PhotonicModelTaskResult, _: TaskSpecification) -> PhotonicModelQuantumTaskResult: - return PhotonicModelQuantumTaskResult.from_object(result) - - -@_format_result.register -def _( - result: AnalogHamiltonianSimulationTaskResult, _: TaskSpecification -) -> AnalogHamiltonianSimulationQuantumTaskResult: - return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) + match result: + case GateModelTaskResult(): + GateModelQuantumTaskResult.cast_result_types(result) + return GateModelQuantumTaskResult.from_object(result) + case ProgramSetTaskResult(): + return ProgramSetQuantumTaskResult.from_object(result, program_set=task_specification) + case AnalogHamiltonianSimulationTaskResult(): + return AnalogHamiltonianSimulationQuantumTaskResult.from_object(result) + case AnnealingTaskResult(): + return AnnealingQuantumTaskResult.from_object(result) + case PhotonicModelTaskResult(): + return PhotonicModelQuantumTaskResult.from_object(result) + case _: + raise TypeError("Invalid result specification type") diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 35163970a..5b13eb824 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -34,7 +34,7 @@ from braket.tracking.tracking_events import _TaskCreationEvent, _TaskStatusEvent -class AwsSession: # noqa: PLR0904 +class AwsSession: """Manage interactions with AWS services.""" class S3DestinationFolder(NamedTuple): diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index ff7d2efdf..42c329513 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -16,7 +16,6 @@ import copy import math from collections.abc import Sequence -from functools import singledispatch from sympy import Float @@ -358,18 +357,14 @@ def __hash__(self): return hash((self.name, self.angle_1, self.angle_2, self.angle_3, self.qubit_count)) -@singledispatch def _angles_equal( angle_1: FreeParameterExpression | float, angle_2: FreeParameterExpression | float ) -> bool: + if isinstance(angle_1, FreeParameterExpression): + return angle_1 == angle_2 return isinstance(angle_2, float) and math.isclose(angle_1, angle_2) -@_angles_equal.register -def _(angle_1: FreeParameterExpression, angle_2: FreeParameterExpression): - return angle_1 == angle_2 - - def angled_ascii_characters(gate: str, angle: FreeParameterExpression | float) -> str: """Generates a formatted ascii representation of an angled gate. diff --git a/src/braket/circuits/basis_state.py b/src/braket/circuits/basis_state.py index b1e55ed88..f4dacb6ed 100644 --- a/src/braket/circuits/basis_state.py +++ b/src/braket/circuits/basis_state.py @@ -1,6 +1,17 @@ -from __future__ import annotations +# 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. -from functools import singledispatch +from __future__ import annotations import numpy as np @@ -50,34 +61,29 @@ def __getitem__(self, item: int): BasisStateInput = int | list[int] | str | BasisState -@singledispatch def _as_tuple(state: BasisStateInput, size: int) -> tuple: - size = size if size is not None else len(state) - if state and len(state) > size: - raise ValueError( - "State value represents a binary sequence of length greater " - "than the specified number of qubits." - ) - return (0,) * (size - len(state)) + tuple(state) - - -@_as_tuple.register -def _(state: int, size: int): - if size is not None and state >= 2**size: - raise ValueError( - "State value represents a binary sequence of length greater " - "than the specified number of qubits." - ) - return tuple(int(x) for x in np.binary_repr(state, size)) - - -@_as_tuple.register -def _(state: str, size: int): - size = size if size is not None else len(state) - if len(state) > size: - raise ValueError( - "State value represents a binary sequence of length greater " - "than the specified number of qubits." - ) - # left-pad to match state size - return (0,) * (size - len(state)) + tuple(int(x) for x in state) + match state: + case int(): + if size is not None and state >= 2**size: + raise ValueError( + "State value represents a binary sequence of length greater " + "than the specified number of qubits." + ) + return tuple(int(x) for x in np.binary_repr(state, size)) + case str(): + size = size if size is not None else len(state) + if len(state) > size: + raise ValueError( + "State value represents a binary sequence of length greater " + "than the specified number of qubits." + ) + # left-pad to match state size + return (0,) * (size - len(state)) + tuple(int(x) for x in state) + case _: + size = size if size is not None else len(state) + if state and len(state) > size: + raise ValueError( + "State value represents a binary sequence of length greater " + "than the specified number of qubits." + ) + return (0,) * (size - len(state)) + tuple(state) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 7d024342e..5f6672f51 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -72,7 +72,7 @@ AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) -class Circuit: # noqa: PLR0904 +class Circuit: """A representation of a quantum circuit that contains the instructions to be performed on a quantum device and the requested result types. diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 9121125b7..dde6ac3be 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -172,46 +172,48 @@ def add(self, instructions: Iterable[Instruction] | Instruction, noise_index: in self._add(instruction, noise_index) def _add(self, instruction: Instruction, noise_index: int = 0) -> None: - operator = instruction.operator - if isinstance(operator, CompilerDirective): - qubit_range = instruction.target.union(instruction.control or QubitSet()) - time = self._handle_compiler_directive(operator, qubit_range) - # For barriers without qubits, use empty qubit set for the key - key_qubits = ( - QubitSet() if operator.name == "Barrier" and not qubit_range else qubit_range - ) - self._moments[MomentsKey(time, key_qubits, MomentType.COMPILER_DIRECTIVE, 0)] = ( - instruction - ) - self._qubits.update(qubit_range) - self._depth = time + 1 - elif isinstance(operator, Noise): - self.add_noise(instruction) - elif isinstance(operator, Gate) and operator.name == "GPhase": - time = self._get_qubit_times(self._max_times.keys()) + 1 - self._number_gphase_in_current_moment += 1 - key = MomentsKey( - time, - QubitSet([]), - MomentType.GLOBAL_PHASE, - 0, - self._number_gphase_in_current_moment, - ) - self._moments[key] = instruction - elif isinstance(operator, Measure): - qubit_range = instruction.target.union(instruction.control) - time = self._get_qubit_times(self._max_times.keys()) + 1 - self._moments[MomentsKey(time, qubit_range, MomentType.MEASURE, noise_index)] = ( - instruction - ) - self._qubits.update(qubit_range) - self._depth = max(self._depth, time + 1) - else: - qubit_range = instruction.target.union(instruction.control) - time = self._update_qubit_times(qubit_range) - self._moments[MomentsKey(time, qubit_range, MomentType.GATE, noise_index)] = instruction - self._qubits.update(qubit_range) - self._depth = max(self._depth, time + 1) + match operator := instruction.operator: + case CompilerDirective(): + qubit_range = instruction.target.union(instruction.control or QubitSet()) + time = self._handle_compiler_directive(operator, qubit_range) + # For barriers without qubits, use empty qubit set for the key + key_qubits = ( + QubitSet() if operator.name == "Barrier" and not qubit_range else qubit_range + ) + self._moments[MomentsKey(time, key_qubits, MomentType.COMPILER_DIRECTIVE, 0)] = ( + instruction + ) + self._qubits.update(qubit_range) + self._depth = time + 1 + case Noise(): + self.add_noise(instruction) + case Gate() if operator.name == "GPhase": + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._number_gphase_in_current_moment += 1 + key = MomentsKey( + time, + QubitSet([]), + MomentType.GLOBAL_PHASE, + 0, + self._number_gphase_in_current_moment, + ) + self._moments[key] = instruction + case Measure(): + qubit_range = instruction.target.union(instruction.control) + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._moments[MomentsKey(time, qubit_range, MomentType.MEASURE, noise_index)] = ( + instruction + ) + self._qubits.update(qubit_range) + self._depth = max(self._depth, time + 1) + case _: + qubit_range = instruction.target.union(instruction.control) + time = self._update_qubit_times(qubit_range) + self._moments[MomentsKey(time, qubit_range, MomentType.GATE, noise_index)] = ( + instruction + ) + self._qubits.update(qubit_range) + self._depth = max(self._depth, time + 1) def _get_qubit_times(self, qubits: QubitSet) -> int: return max([self._max_time_for_qubit(qubit) for qubit in qubits] + [self._time_all_qubits]) @@ -265,15 +267,16 @@ def sort_moments(self) -> None: for key, instruction in self._moments.items(): moment_copy[key] = instruction - if key.moment_type == MomentType.READOUT_NOISE: - key_readout_noise.append(key) - elif key.moment_type == MomentType.INITIALIZATION_NOISE: - key_initialization_noise.append(key) - elif key.moment_type == MomentType.MEASURE: - last_measure = key.time - key_noise.append(key) - else: - key_noise.append(key) + match key.moment_type: + case MomentType.READOUT_NOISE: + key_readout_noise.append(key) + case MomentType.INITIALIZATION_NOISE: + key_initialization_noise.append(key) + case MomentType.MEASURE: + last_measure = key.time + key_noise.append(key) + case _: + key_noise.append(key) for key in key_initialization_noise: sorted_moment[key] = moment_copy[key] diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 8aa635575..1dbf3baef 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -182,14 +182,15 @@ def get_instructions_by_type(self) -> NoiseModelInstructions: gate_noise = [] readout_noise = [] for item in self._instructions: - if isinstance(item.criteria, InitializationCriteria): - init_noise.append(item) - elif isinstance(item.criteria, MeasureCriteria): - readout_noise.append(item) - elif isinstance(item.criteria, CircuitInstructionCriteria): - gate_noise.append(item) - elif isinstance(item.criteria, ResultTypeCriteria): - readout_noise.append(item) + match item.criteria: + case InitializationCriteria(): + init_noise.append(item) + case MeasureCriteria(): + readout_noise.append(item) + case CircuitInstructionCriteria(): + gate_noise.append(item) + case ResultTypeCriteria(): + readout_noise.append(item) return NoiseModelInstructions( initialization_noise=init_noise, gate_noise=gate_noise, diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index db8d5db21..b80b42171 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -1,3 +1,16 @@ +# 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. + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index f50a1e206..8599ea9a2 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -14,10 +14,8 @@ from __future__ import annotations import operator -from functools import reduce, singledispatch -from typing import NoReturn +from functools import reduce -import braket.ir.jaqcd.shared_models as models from braket.default_simulator.openqasm.interpreter import VerbatimBoxDelimiter from braket.ir.jaqcd import ( Amplitude, @@ -31,8 +29,7 @@ from braket.ir.jaqcd.program_v1 import Results import braket.circuits.gates as braket_gates -import braket.circuits.result_types as ResultTypes # noqa: N812 -from braket.circuits import Observable, noises, observables +from braket.circuits import Observable, ResultType, noises, observables, result_types from braket.circuits.compiler_directives import EndVerbatimBox, StartVerbatimBox from braket.experimental_capabilities.iqm.classical_control import CCPRx, MeasureFF @@ -112,89 +109,47 @@ } -def get_observable(obs: models.Observable | list) -> Observable: - """Gets the observable. - - Args: - obs (models.Observable | list): The observable(s) to get translated. - - Returns: - Observable: The translated observable. - """ - return _get_observable(obs) - - -@singledispatch -def _get_observable(obs: models.Observable | list) -> Observable: +def _get_observable(obs: str) -> Observable: + if isinstance(obs, str): + return getattr(observables, obs.upper())() raise NotImplementedError -@_get_observable.register(list) -def _(obs: Observable) -> NoReturn: - raise NotImplementedError - - -@_get_observable.register(str) -def _(name: str): - return getattr(observables, name.upper())() - - -def get_tensor_product(observable: models.Observable | list) -> Observable: +def get_tensor_product(observable: list[str]) -> Observable: """Generate an braket circuit observable Args: - observable (Observable | list): ir observable or a matrix + observable (list[str]): ir observable or a matrix Returns: Observable: braket circuit observable """ - circuit_observable = [get_observable(obs) for obs in observable] + circuit_observable = [_get_observable(obs) for obs in observable] return reduce(operator.matmul, circuit_observable) -@singledispatch -def _braket_result_to_result_type(result: Results) -> None: - raise TypeError(f"Result type {type(result).__name__} is not supported") - - -def braket_result_to_result_type(result: Results) -> None: +def braket_result_to_result_type(result: Results) -> ResultType: return _braket_result_to_result_type(result) -@_braket_result_to_result_type.register(Amplitude) -def _(result: Results) -> Amplitude: - return ResultTypes.Amplitude(state=result.states) - - -@_braket_result_to_result_type.register(Expectation) -def _(result: Results) -> Expectation: - tensor_product = get_tensor_product(result.observable) - - return ResultTypes.Expectation(observable=tensor_product, target=result.targets) - - -@_braket_result_to_result_type.register(Probability) -def _(result: Results) -> Probability: - return ResultTypes.Probability(result.targets) - - -@_braket_result_to_result_type.register(Sample) -def _(result: Results) -> Sample: - tensor_product = get_tensor_product(result.observable) - return ResultTypes.Sample(observable=tensor_product, target=result.targets) - - -@_braket_result_to_result_type.register(StateVector) -def _(result: Results) -> StateVector: - return ResultTypes.StateVector() - - -@_braket_result_to_result_type.register(DensityMatrix) -def _(result: Results): - return ResultTypes.DensityMatrix(target=result.targets) - - -@_braket_result_to_result_type.register(Variance) -def _(result: Results): - tensor_product = get_tensor_product(result.observable) - return ResultTypes.Variance(observable=tensor_product, target=result.targets) +def _braket_result_to_result_type(result: Results) -> ResultType: + match result: + case Expectation(observable=observable, targets=targets): + tensor_product = get_tensor_product(observable) + return result_types.Expectation(observable=tensor_product, target=targets) + case Variance(observable=observable, targets=targets): + tensor_product = get_tensor_product(observable) + return result_types.Variance(observable=tensor_product, target=targets) + case Sample(observable=observable, targets=targets): + tensor_product = get_tensor_product(observable) + return result_types.Sample(observable=tensor_product, target=targets) + case Probability(targets=targets): + return result_types.Probability(targets) + case Amplitude(states=states): + return result_types.Amplitude(state=states) + case StateVector(): + return result_types.StateVector() + case DensityMatrix(targets=targets): + return result_types.DensityMatrix(target=targets) + case _: + raise TypeError(f"Result type {type(result).__name__} is not supported") diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 13eb90e43..977960bf5 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -10,6 +10,7 @@ # 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. + from __future__ import annotations import base64 diff --git a/src/braket/jobs/metrics.py b/src/braket/jobs/metrics.py index 5e58b4995..7de22c07d 100644 --- a/src/braket/jobs/metrics.py +++ b/src/braket/jobs/metrics.py @@ -10,6 +10,7 @@ # 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. + from __future__ import annotations import time diff --git a/src/braket/jobs/metrics_data/definitions.py b/src/braket/jobs/metrics_data/definitions.py index a3e77a783..2091a9c8d 100644 --- a/src/braket/jobs/metrics_data/definitions.py +++ b/src/braket/jobs/metrics_data/definitions.py @@ -10,6 +10,7 @@ # 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. + from enum import Enum, unique diff --git a/src/braket/jobs/quantum_job.py b/src/braket/jobs/quantum_job.py index 32c660bcf..f4264a699 100644 --- a/src/braket/jobs/quantum_job.py +++ b/src/braket/jobs/quantum_job.py @@ -10,6 +10,7 @@ # 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. + from __future__ import annotations from abc import ABC, abstractmethod diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 75ce4437e..d8ee30616 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -10,6 +10,7 @@ # 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. + from __future__ import annotations import importlib.util diff --git a/src/braket/program_sets/parameter_sets.py b/src/braket/program_sets/parameter_sets.py index 89c3063f3..c23c21ab8 100644 --- a/src/braket/program_sets/parameter_sets.py +++ b/src/braket/program_sets/parameter_sets.py @@ -51,14 +51,15 @@ def __init__( """ ParameterSets._validate_combinations(parameter_sets, keys=keys, values=values, **kwargs) if parameter_sets: - if isinstance(parameter_sets, ParameterSets): - self._inputs = parameter_sets._inputs - elif isinstance(parameter_sets, Mapping): - self._inputs = ParameterSets._mapping_to_dict(parameter_sets) - elif isinstance(parameter_sets, Sequence): - self._inputs = ParameterSets._sequence_to_dict(parameter_sets) - else: - raise TypeError(f"Unsupported type {type(parameter_sets)} for ParameterSets") + match parameter_sets: + case ParameterSets(_inputs=inputs): + self._inputs = inputs + case Mapping(): + self._inputs = ParameterSets._mapping_to_dict(parameter_sets) + case Sequence(): + self._inputs = ParameterSets._sequence_to_dict(parameter_sets) + case _: + raise TypeError(f"Unsupported type {type(parameter_sets)} for ParameterSets") elif keys: self._inputs = ParameterSets._mapping_to_dict(dict(zip(keys, values, strict=True))) elif kwargs: diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index e787d0ce3..0d2a2a434 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -49,7 +49,7 @@ class _ParseState: frame_data: dict[str, _FrameState] -class _ApproximationParser(QASMVisitor[_ParseState]): # noqa: PLR0904 +class _ApproximationParser(QASMVisitor[_ParseState]): """Walk the AST and build the output signal amplitude, frequency and phases for each channel. """ @@ -254,7 +254,7 @@ def visit_UnaryExpression(self, node: ast.UnaryExpression, context: _ParseState) return ~self.visit(node.expression, context) raise NotImplementedError - def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: # noqa: C901, PLR0911, PLR0912 + def visit_BinaryExpression(self, node: ast.BinaryExpression, context: _ParseState) -> Any: # noqa: C901, PLR0912 """Visit Binary Expression. node.lhs, node.rhs, node.op 1+2 diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index 573416949..6809ea263 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -341,15 +341,14 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: """ if gate_model_task_result.resultTypes: for result_type in gate_model_task_result.resultTypes: - type = result_type.type.type - if type == "amplitude": - for state in result_type.value: - result_type.value[state] = complex(*result_type.value[state]) - - elif type == "probability": - result_type.value = np.array(result_type.value) - elif type == "statevector": - result_type.value = np.array(list(starmap(complex, result_type.value))) + match result_type.type.type: + case "amplitude": + for state in result_type.value: + result_type.value[state] = complex(*result_type.value[state]) + case "probability": + result_type.value = np.array(result_type.value) + case "statevector": + result_type.value = np.array(list(starmap(complex, result_type.value))) @staticmethod def _calculate_result_types( @@ -363,41 +362,41 @@ def _calculate_result_types( ir_observable = result_type.get("observable") observable = observable_from_ir(ir_observable) if ir_observable else None targets = result_type.get("targets") - rt_type = result_type["type"] - if rt_type == "probability": - value = GateModelQuantumTaskResult._probability_from_measurements( - measurements, measured_qubits, targets - ) - casted_result_type = Probability(targets=targets) - elif rt_type == "sample": - value = GateModelQuantumTaskResult._calculate_for_targets( - samples_from_measurements, - measurements, - measured_qubits, - observable, - targets, - ) - casted_result_type = Sample(targets=targets, observable=ir_observable) - elif rt_type == "variance": - value = GateModelQuantumTaskResult._calculate_for_targets( - GateModelQuantumTaskResult._variance_from_measurements, - measurements, - measured_qubits, - observable, - targets, - ) - casted_result_type = Variance(targets=targets, observable=ir_observable) - elif rt_type == "expectation": - value = GateModelQuantumTaskResult._calculate_for_targets( - expectation_from_measurements, - measurements, - measured_qubits, - observable, - targets, - ) - casted_result_type = Expectation(targets=targets, observable=ir_observable) - else: - raise ValueError(f"Unknown result type {rt_type}") + match rt_type := result_type["type"]: + case "probability": + value = GateModelQuantumTaskResult._probability_from_measurements( + measurements, measured_qubits, targets + ) + casted_result_type = Probability(targets=targets) + case "sample": + value = GateModelQuantumTaskResult._calculate_for_targets( + samples_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + casted_result_type = Sample(targets=targets, observable=ir_observable) + case "variance": + value = GateModelQuantumTaskResult._calculate_for_targets( + GateModelQuantumTaskResult._variance_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + casted_result_type = Variance(targets=targets, observable=ir_observable) + case "expectation": + value = GateModelQuantumTaskResult._calculate_for_targets( + expectation_from_measurements, + measurements, + measured_qubits, + observable, + targets, + ) + casted_result_type = Expectation(targets=targets, observable=ir_observable) + case _: + raise ValueError(f"Unknown result type {rt_type}") result_types.append(ResultTypeValue.construct(type=casted_result_type, value=value)) return result_types diff --git a/src/braket/tasks/local_quantum_task.py b/src/braket/tasks/local_quantum_task.py index 3243b4ff6..275847829 100644 --- a/src/braket/tasks/local_quantum_task.py +++ b/src/braket/tasks/local_quantum_task.py @@ -10,6 +10,7 @@ # 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. + from __future__ import annotations import asyncio diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 3b6214759..63416516c 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -15,7 +15,6 @@ from datetime import timedelta from decimal import Decimal -from functools import singledispatchmethod from typing import Any from braket.tracking.pricing import price_search @@ -24,6 +23,7 @@ _TaskCompletionEvent, _TaskCreationEvent, _TaskStatusEvent, + _TrackingEvent, ) MIN_SIMULATOR_DURATION = timedelta(milliseconds=3000) @@ -173,42 +173,36 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: return stats - @singledispatchmethod - def _recieve_internal(self, event: _TaskCreationEvent) -> None: - raise ValueError(f"Event type {type(event)} is not supported") - - @_recieve_internal.register - def _(self, event: _TaskCreationEvent) -> None: - self._resources[event.arn] = { - "shots": event.shots, - "device": event.device, - "status": "CREATED", - "job_task": event.is_job_task, - } - - @_recieve_internal.register - def _(self, event: _TaskStatusEvent) -> None: + def _recieve_internal(self, event: _TrackingEvent) -> None: resources = self._resources - # Update task data corresponding to the arn only if it exists in resources - if event.arn in resources: - resources[event.arn]["status"] = event.status - - @_recieve_internal.register - def _(self, event: _TaskCompletionEvent) -> None: - resources = self._resources - # Update task completion data corresponding to the arn only if it exists in resources - if event.arn in resources: - resources[event.arn]["status"] = event.status - has_reservation_arn = event.has_reservation_arn - resources[event.arn]["has_reservation_arn"] = has_reservation_arn - if event.execution_duration: - duration = timedelta(milliseconds=event.execution_duration) - resources[event.arn]["execution_duration"] = duration - resources[event.arn]["billed_duration"] = ( - timedelta(milliseconds=0) - if has_reservation_arn - else max(duration, MIN_SIMULATOR_DURATION) - ) + match event: + case _TaskCreationEvent(arn, shots, is_job_task, device): + resources[arn] = { + "shots": shots, + "device": device, + "status": "CREATED", + "job_task": is_job_task, + } + case _TaskStatusEvent(arn, status): + # Update task data corresponding to the arn only if it exists in resources + if arn in resources: + resources[arn]["status"] = status + case _TaskCompletionEvent(arn, execution_duration, status, has_reservation_arn): + # Update task completion data corresponding to the arn + # only if it exists in resources + if arn in resources: + resources[arn]["status"] = status + resources[arn]["has_reservation_arn"] = has_reservation_arn + if execution_duration: + duration = timedelta(milliseconds=execution_duration) + resources[arn]["execution_duration"] = duration + resources[arn]["billed_duration"] = ( + timedelta(milliseconds=0) + if has_reservation_arn + else max(duration, MIN_SIMULATOR_DURATION) + ) + case _: + raise ValueError(f"Event type {type(event)} is not supported") def _get_qpu_task_cost(task_arn: str, details: dict) -> Decimal: diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index b3e31e656..373c2e957 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -13,7 +13,7 @@ import concurrent.futures import math -from typing import Any, Union +from typing import Any import numpy as np @@ -49,7 +49,7 @@ def qubit_ordering_testing(device: Device, run_kwargs: dict[str, Any]): def no_result_types_testing( - program: Union[Circuit, OpenQasmProgram], + program: Circuit | OpenQasmProgram, device: Device, run_kwargs: dict[str, Any], expected: dict[str, float], diff --git a/test/integ_tests/test_density_matrix_simulator.py b/test/integ_tests/test_density_matrix_simulator.py index 1fba5ebcb..b562d2836 100644 --- a/test/integ_tests/test_density_matrix_simulator.py +++ b/test/integ_tests/test_density_matrix_simulator.py @@ -1,3 +1,16 @@ +# 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. + import math import numpy as np diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index c8994de34..cb4a8192e 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - import pytest from braket.aws import AwsDevice diff --git a/test/integ_tests/test_pulse.py b/test/integ_tests/test_pulse.py index 4899eb28c..41f79adc0 100644 --- a/test/integ_tests/test_pulse.py +++ b/test/integ_tests/test_pulse.py @@ -1,3 +1,16 @@ +# 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. + import math import numpy as np diff --git a/test/integ_tests/test_queue_information.py b/test/integ_tests/test_queue_information.py index 7e7590a80..25afd1b8c 100644 --- a/test/integ_tests/test_queue_information.py +++ b/test/integ_tests/test_queue_information.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - from braket.aws import AwsDevice from braket.aws.queue_information import ( HybridJobQueueInfo, diff --git a/test/integ_tests/test_user_agents.py b/test/integ_tests/test_user_agents.py index 009a9a4a4..6cd83bd21 100644 --- a/test/integ_tests/test_user_agents.py +++ b/test/integ_tests/test_user_agents.py @@ -1,3 +1,16 @@ +# 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. + from botocore import awsrequest import braket._schemas as braket_schemas @@ -17,7 +30,6 @@ def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): assert bytes(agent, "utf8") in user_agent aws_session.braket_client.meta.events.register("before-send.braket", assert_in_user_agent) - aws_session.search_devices() @@ -29,5 +41,4 @@ def assert_in_user_agent(request: awsrequest.AWSPreparedRequest, **kwargs): assert b"foo/1.0" in user_agent aws_session.braket_client.meta.events.register("before-send.braket", assert_in_user_agent) - aws_session.search_devices() diff --git a/test/unit_tests/braket/circuits/noise/test_measure_criteria.py b/test/unit_tests/braket/circuits/noise/test_measure_criteria.py index cb67ee0f9..17e3b3201 100644 --- a/test/unit_tests/braket/circuits/noise/test_measure_criteria.py +++ b/test/unit_tests/braket/circuits/noise/test_measure_criteria.py @@ -1,3 +1,16 @@ +# 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. + import pytest from unittest.mock import Mock diff --git a/test/unit_tests/braket/circuits/test_basis_state.py b/test/unit_tests/braket/circuits/test_basis_state.py index 0ce4eab38..42af99171 100644 --- a/test/unit_tests/braket/circuits/test_basis_state.py +++ b/test/unit_tests/braket/circuits/test_basis_state.py @@ -1,3 +1,16 @@ +# 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. + import pytest from braket.circuits.basis_state import BasisState diff --git a/test/unit_tests/braket/circuits/test_result_type.py b/test/unit_tests/braket/circuits/test_result_type.py index bb543b7c0..58bd6e018 100644 --- a/test/unit_tests/braket/circuits/test_result_type.py +++ b/test/unit_tests/braket/circuits/test_result_type.py @@ -10,6 +10,7 @@ # 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. + import re import pytest diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index caefc83d8..ebb1a603e 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -16,7 +16,7 @@ import sys import textwrap import warnings -from typing import Any, Optional +from typing import Any from unittest.mock import Mock, patch import pytest @@ -224,8 +224,8 @@ def run( self, program: ir.jaqcd.Program, qubits: int, - shots: Optional[int], - inputs: Optional[dict[str, float]], + shots: int | None, + inputs: dict[str, float] | None, *args, **kwargs, ) -> dict[str, Any]: @@ -264,8 +264,8 @@ class DummyJaqcdSimulator(BraketSimulator): def run( self, program: ir.jaqcd.Program, - qubits: Optional[int] = None, - shots: Optional[int] = None, + qubits: int | None = None, + shots: int | None = None, *args, **kwargs, ) -> dict[str, Any]: @@ -385,7 +385,7 @@ def run( class DummyProgramDensityMatrixSimulator(BraketSimulator): def run( - self, program: ir.openqasm.Program, shots: Optional[int], *args, **kwargs + self, program: ir.openqasm.Program, shots: int | None, *args, **kwargs ) -> dict[str, Any]: self._shots = shots return GATE_MODEL_RESULT diff --git a/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py b/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py index 1f37f0be3..4a087930d 100644 --- a/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/passes/test_connectivity_validator.py @@ -1,3 +1,16 @@ +# 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. + import networkx as nx import numpy as np import pytest diff --git a/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py b/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py index 956bb3321..ef3283032 100644 --- a/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py +++ b/test/unit_tests/braket/emulation/passes/test_gate_connectivity_validator.py @@ -1,3 +1,16 @@ +# 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. + import networkx as nx import numpy as np import pytest diff --git a/test/unit_tests/braket/emulation/passes/test_gate_validator.py b/test/unit_tests/braket/emulation/passes/test_gate_validator.py index 39ff8276c..8089be049 100644 --- a/test/unit_tests/braket/emulation/passes/test_gate_validator.py +++ b/test/unit_tests/braket/emulation/passes/test_gate_validator.py @@ -1,3 +1,16 @@ +# 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. + import numpy as np import pytest diff --git a/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py b/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py index c7c7cf878..791eb00ff 100644 --- a/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py +++ b/test/unit_tests/braket/emulation/passes/test_qubit_count_validator.py @@ -1,3 +1,16 @@ +# 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. + import numpy as np import pytest diff --git a/test/unit_tests/braket/emulation/passes/test_result_type_validator.py b/test/unit_tests/braket/emulation/passes/test_result_type_validator.py index 8087e46ec..420325daf 100644 --- a/test/unit_tests/braket/emulation/passes/test_result_type_validator.py +++ b/test/unit_tests/braket/emulation/passes/test_result_type_validator.py @@ -1,3 +1,16 @@ +# 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. + import pytest import re from braket.circuits import Circuit, Observable diff --git a/test/unit_tests/braket/emulation/test_emulator.py b/test/unit_tests/braket/emulation/test_emulator.py index d8d2401cc..e9731f4d0 100644 --- a/test/unit_tests/braket/emulation/test_emulator.py +++ b/test/unit_tests/braket/emulation/test_emulator.py @@ -1,3 +1,16 @@ +# 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. + import re from unittest.mock import Mock diff --git a/test/unit_tests/braket/jobs/test_environment_variables.py b/test/unit_tests/braket/jobs/test_environment_variables.py index dd1f12407..6ef0311c6 100644 --- a/test/unit_tests/braket/jobs/test_environment_variables.py +++ b/test/unit_tests/braket/jobs/test_environment_variables.py @@ -1,3 +1,16 @@ +# 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. + import json import os import tempfile diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index fa133ce52..0a08b544e 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -1,3 +1,16 @@ +# 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. + import ast import importlib import inspect diff --git a/test/unit_tests/braket/jobs/test_metrics.py b/test/unit_tests/braket/jobs/test_metrics.py index 1670cee3e..81d419f93 100644 --- a/test/unit_tests/braket/jobs/test_metrics.py +++ b/test/unit_tests/braket/jobs/test_metrics.py @@ -1,3 +1,16 @@ +# 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. + from unittest.mock import patch import pytest diff --git a/test/unit_tests/braket/jobs/test_serialization.py b/test/unit_tests/braket/jobs/test_serialization.py index bbd1c6238..bb03fae4e 100644 --- a/test/unit_tests/braket/jobs/test_serialization.py +++ b/test/unit_tests/braket/jobs/test_serialization.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - import pytest from braket.jobs.serialization import deserialize_values, serialize_values diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py index 4c0e2105c..f2eb68d22 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import Union from unittest.mock import Mock import numpy as np @@ -1158,7 +1157,7 @@ def verify_results(results, expected_amplitudes, expected_frequencies, expected_ assert _all_close(results.phases[frame_id], expected_phases[frame_id], 1e-10) -def to_dict(frames: Union[Frame, list]): +def to_dict(frames: Frame | list): if not isinstance(frames, list): frames = [frames] frame_dict = dict() diff --git a/test/unit_tests/braket/test_imports.py b/test/unit_tests/braket/test_imports.py index 728c6311f..41751af3a 100644 --- a/test/unit_tests/braket/test_imports.py +++ b/test/unit_tests/braket/test_imports.py @@ -1,3 +1,16 @@ +# 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. + import importlib import multiprocessing import os diff --git a/test/unit_tests/braket/tracking/test_pricing.py b/test/unit_tests/braket/tracking/test_pricing.py index 0d6b4348f..bbf059667 100644 --- a/test/unit_tests/braket/tracking/test_pricing.py +++ b/test/unit_tests/braket/tracking/test_pricing.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. - import io from unittest.mock import patch From 42e7cd1a0be196c5d6c4c2b07584d4d847320b3e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Sep 2025 16:16:24 +0000 Subject: [PATCH 346/347] prepare release v1.102.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca02ca4bf..5688b2c48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.102.2 (2025-09-11) + +### Bug Fixes and Other Changes + + * Python 3.10 syntax + ## v1.102.1 (2025-09-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 66b26c957..d4f936127 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.102.2.dev0" +__version__ = "1.102.2" From a76c4e2b50e9c3a199e4461b5b2a3ba1f3b79f4e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Sep 2025 16:16:24 +0000 Subject: [PATCH 347/347] update development version to v1.102.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d4f936127..115454f8f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.102.2" +__version__ = "1.102.3.dev0"